[Angular 基础] - 自定义事件 & 自定义属性

[Angular 基础] - 自定义事件 & 自定义属性


之前的笔记:

以上是能够实现渲染静态页面的基础


之前的内容主要学习了怎么通过绑定原生 HTML(style, class, click 等) 和 Angular(ngFor, (click), {``{ string interpolation }} 等) 的事件和属性动态渲染静态页面,这里开始讲组件沟通之间的部分,让页面开始真正的动起来

也就是 组件(component)指令(directives) 的进阶学习

设置项目

目前项目的结构如下:

bash 复制代码
src/app/
├── app.component.css
├── app.component.html
├── app.component.ts
├── app.module.ts
├── cockpit
│   ├── cockpit.component.css
│   ├── cockpit.component.html
│   └── cockpit.component.ts
└── server-element
    ├── server-element.component.css
    ├── server-element.component.html
    └── server-element.component.ts

3 directories, 10 files

app

其中最基层的 app 的作用是存储一个 serverList,并且使用 serverList 去渲染对应的 cockpitserver-element,具体文件如下:

  • VM 层

    ts 复制代码
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
    })
    export class AppComponent {
      serverElements = [];
    }
  • V 层

    html 复制代码
    <div class="container">
      <app-cockpit></app-cockpit>
      <hr />
      <div class="row">
        <div class="col-xs-12">
          <app-server-element
            *ngFor="let element of serverElements"
          ></app-server-element>
        </div>
      </div>
    </div>

    这里就会开始涉及组件之间的沟通:

    • cockpit 会创建一个 server,并且将数据添加到 serverElements
    • server-element 会接受 element,也就是 for 循环里的元素

cockpit

有些无关紧要的说明:

駕駛艙(英語:Cockpit),是飞行员控制飛機的座艙,通常位於一架飛機的前端。除了早期的部分飛機,如今大部分飛機的駕駛艙采用密閉式的設計。

这里命名为 cockpit 大概是因为一个 server 既可以是 server,也可以是一个 blueprint。这个不用细究 class/object 的区别,主要还是自定义事件和属性方面的问题

  • VM 层

    ts 复制代码
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-cockpit',
      templateUrl: './cockpit.component.html',
      styleUrl: './cockpit.component.css',
    })
    export class CockpitComponent {
      newServerName = '';
      newServerContent = '';
    
      onAddServer() {
      }
    
      onAddBlueprint() {
    }
  • V 层

    html 复制代码
    <div class="row">
      <div class="col-xs-12">
        <p>Add new Servers or blueprints!</p>
        <label>Server Name</label>
        <input type="text" class="form-control" [(ngModel)]="newServerName" />
        <label>Server Content</label>
        <input type="text" class="form-control" [(ngModel)]="newServerContent" />
        <br />
        <div class="btn-toolbar">
          <button class="btn btn-primary" (click)="onAddServer()">
            Add Server
          </button>
          <button class="btn btn-primary" (click)="onAddBlueprint()">
            Add Server Blueprint
          </button>
        </div>
      </div>
    </div>

server-element

这里会接受一个 server,并且将其渲染到页面上

  • VM 层

    typescript 复制代码
    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-server-element',
      templateUrl: './server-element.component.html',
      styleUrl: './server-element.component.css',
    })
    export class ServerElementComponent {}
  • V 层

    html 复制代码
    <div class="panel panel-default">
      <div class="panel-heading">{{ element.name }}</div>
      <div class="panel-body">
        <p>
          <strong *ngIf="element.type === 'server'" style="color: red"
            >{{ element.content }}</strong
          >
          <em *ngIf="element.type === 'blueprint'">{{ element.content }}</em>
        </p>
      </div>
    </div>

此时因为组件之间的交流还没有完成,所以代码运行肯定会失败的,不过最基础的是已经完成了

绑定自定义属性

首先是从渲染 server-listserver-element 开始,所以需要将 cockpit 内的东西注释掉,以防报错

如果不会报错的话则可以忽略,我后面又做了点修改......

model

先新建一个 server-element 的 model 让其他文件引用,我改了下结构,现在 model 在这里:

bash 复制代码
❯ tree src/app/
src/app/
├── model
│   └── server-element.model.ts

内容如下:

typescript 复制代码
export class ServerElement {
  constructor(
    public name: string,
    public type: 'server' | 'blueprint',
    public content: string
  ) {}
}

app VM 层

这里主要就是在数组里放一个数据,新增代码如下:

typescript 复制代码
export class AppComponent {
  serverElements: ServerElement[] = [
    { type: 'server', name: 'Testserver', content: 'Just a test!' },
  ];
}

app V 层

这里会更新一下代码,绑定 自定义属性 element

html 复制代码
<div class="container">
  <app-cockpit></app-cockpit>
  <hr />
  <div class="row">
    <div class="col-xs-12">
      <app-server-element
        *ngFor="let serverElement of serverElements"
        [element]="serverElement"
      ></app-server-element>
    </div>
  </div>
</div>

其中 [element]="serverElement" 就是新增的代码,也就是绑定的 自定义属性

server-element V 层

这里是选择接受参数的地方,已经从上面的 V 层知道传进来的自定义属性是 element,因此这里就用 element 作为变量名:

html 复制代码
<div class="panel panel-default">
  <div class="panel-heading">{{ element.name }}</div>
  <div class="panel-body">
    <p>
      <strong *ngIf="element.type === 'server'" style="color: red"
        >{{ element.content }}</strong
      >
      <em *ngIf="element.type === 'blueprint'">{{ element.content }}</em>
    </p>
  </div>
</div>

server-element VM 层

VM 层是掌管数据的地方,因此 VM 层还需要声明一下 element 的存在:

typescript 复制代码
import { Component } from '@angular/core';
import { ServerElement } from '../model/server-element.model';

@Component({
  selector: 'app-server-element',
  templateUrl: './server-element.component.html',
  styleUrl: './server-element.component.css',
})
export class ServerElementComponent {
  // 不做类型声明也不会报错,但是会有简易
  element: ServerElement;
}

这时候效果如下:

Angular 渲染了一个元素,但是这个元素是空的,这个原因是因为 scoping 的问题,element 本质上还是只对父组件------即 app 组件------可见,如果想让它在子组件里也能被访问到,需要用一个新的装饰器:@Input(),修改如下:

typescript 复制代码
export class ServerElementComponent {
  @Input() element: ServerElement;
}

随后即可正常渲染:

⚠️:Input 需要从 @angular/core 中导入

自定义属性的 alias

有的时候会想要设置 alias,而非使用传递过来的变量名------比如说可能父元素会创建一个事件然后传递 event 到子元素中,子元素则可以根据需求去重命名这是一个 mouseEvent, inputEvent, formEvent 或是其他,修改方法如下:

typescript 复制代码
export class ServerElementComponent {
  // () 内的才是父组件里使用的变量名
  @Input('element') aliasElement: ServerElement;
}

这个时候,对于当前组件来说,可访问的变量为 aliasElement,因此 V 层也需要进行对应的修改:

html 复制代码
<div class="panel panel-default">
  <div class="panel-heading">{{ aliasElement.name }}</div>
  <div class="panel-body">
    <p>
      <strong *ngIf="aliasElement.type === 'server'" style="color: red"
        >{{ aliasElement.content }}</strong
      >
      <em *ngIf="aliasElement.type === 'blueprint'"
        >{{ aliasElement.content }}</em
      >
    </p>
  </div>
</div>

绑定自定义事件

这个时候需要将 cockpit 里的代码还原

这里同样需要注意的一点是数据的传输方向,在父组件中,只有 serverElements 被声明了,具体的添加事件是发生在子组件中的,也就是说,事件的传输方向并不是由父组件向子组件进行传输,而是从子组件传递到父组件。准确的说也不是传送,而是发送(emit 🚀)。和 React 相反,Angular 的事件通常情况下是从子组件发送到父组件,父组件通过监听事件进行对应的处理

其实这个处理大方向和上面绑定自定义属性差不多,最大的差别就是 flow

cockpit VM 层

实现如下:

typescript 复制代码
export class CockpitComponent {
  @Output() serverCreated = new EventEmitter<Omit<ServerElement, 'type'>>();
  @Output() blueprintCreated = new EventEmitter<Omit<ServerElement, 'type'>>();
  newServerName = '';
  newServerContent = '';

  onAddServer() {
    this.serverCreated.emit({
      name: this.newServerName,
      content: this.newServerContent,
    });
  }

  onAddBlueprint() {
    this.blueprintCreated.emit({
      name: this.newServerName,
      content: this.newServerContent,
    });
  }
}

⚠️:这里的 Output 同样需要从 angular-core 导入

👀:注意这里的语法,这是一个 EventEmitter,并且类型是 Output。这也说明了事件的方向是自下而上,而非自上而下------对比 React,React 将 event handler 从上往下传,并在子元素进行调用

cockpit V 层

保持不变

app VM 层

变动如下

typescript 复制代码
export class AppComponent {
  serverElements: ServerElement[] = [
    { type: 'server', name: 'Testserver', content: 'Just a test!' },
  ];
  serverData: ServerElement;

  onServerAdded(serverData: Omit<ServerElement, 'type'>) {
    this.serverElements.push({
      type: 'server',
      name: serverData.name,
      content: serverData.content,
    });
  }

  onBlueprintAdded(blueprintData: Omit<ServerElement, 'type'>) {
    this.serverElements.push({
      type: 'blueprint',
      name: blueprintData.name,
      content: blueprintData.content,
    });
  }
}

⚠️:Omit 是 TypeScript 的语法,详细的使用方法可以查看官方文档:Utility Types

app V 层

变动如下:

html 复制代码
<div class="container">
  <app-cockpit
    (serverCreated)="onServerAdded($event)"
    (blueprintCreated)="onBlueprintAdded($event)"
  ></app-cockpit>
  <hr />
  <div class="row">
    <div class="col-xs-12">
      <app-server-element
        *ngFor="let serverElement of serverElements"
        [element]="serverElement"
      ></app-server-element>
    </div>
  </div>
</div>

实现后效果如下:

自定义事件的 alias

这个和自定义属性的方式实现的也差不多:

typescript 复制代码
import { Component, EventEmitter, Output } from '@angular/core';
import { ServerElement } from '../model/server-element.model';

@Component({
  selector: 'app-cockpit',
  templateUrl: './cockpit.component.html',
  styleUrl: './cockpit.component.css',
})
export class CockpitComponent {
  @Output('serverCreated') svCreated = new EventEmitter<
    Omit<ServerElement, 'type'>
  >();
  @Output('blueprintCreated') bpCreated = new EventEmitter<
    Omit<ServerElement, 'type'>
  >();
  newServerName = '';
  newServerContent = '';

  onAddServer() {
    this.svCreated.emit({
      name: this.newServerName,
      content: this.newServerContent,
    });
  }

  onAddBlueprint() {
    this.bpCreated.emit({
      name: this.newServerName,
      content: this.newServerContent,
    });
  }
}

同样是 () 内的代表外部的变量名,而声明的则是组件内部可用的名称


到这里就实现了数据和事件的跨组件交流

相关推荐
加班是不可能的,除非双倍日工资3 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi3 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip4 小时前
vite和webpack打包结构控制
前端·javascript
excel4 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国4 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼4 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy4 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT5 小时前
promise & async await总结
前端
Jerry说前后端5 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化
画个太阳作晴天5 小时前
A12预装app
linux·服务器·前端