天天看點

Angular 2 Output

Angular 2 Output

Output 是屬性裝飾器,用來定義元件内的輸出屬性。在 Angular 2 Input 文章中,我們介紹了 Input 裝飾器的作用,也了解了當應用啟動時,Angular 會從根元件開始啟動,并解析整棵元件樹,資料由上而下流下下一級子元件。而我們今天介紹的 Output 裝飾器,是用來實作子元件将資訊通過事件的形式通知到父級元件。具體如下圖所示:

Angular 2 Output

在介紹 Output 屬性裝飾器前,我們先來介紹一下

EventEmitter

這個幕後英雄:

EventEmitter 用來觸發自定義事件,具體使用示例如下:

let numberEmitter: EventEmitter<number> = new EventEmitter<number>(); 
numberEmitter.subscribe((value: number) => console.log(value));
numberEmitter.emit(10);
           

在 Angular 2 中的 EventEmitter 應用場景是:子指令建立一個 EventEmitter 執行個體,并将其作為輸出屬性導出。子指令調用已建立的 EventEmitter 執行個體中的 emit(payload) 方法來觸發一個事件,父指令通過事件綁定

(eventName)

的方式監聽該事件,并通過 $event 對象來擷取 payload 對象。是不是感覺有點抽象,我們馬上實戰一下。

@Output()

counter.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
    selector: 'exe-counter',
    template: `
      <p>目前值: {{ count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    `
})
export class CounterComponent {
    @Input() count: number = 0;

    @Output() change: EventEmitter<number> = new EventEmitter<number>();

    increment() {
        this.count++;
        this.change.emit(this.count);
    }

    decrement() {
        this.count--;
        this.change.emit(this.count);
    }
}
           

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'exe-app',
  template: `
   <p>{{changeMsg}}</p> 
   <exe-counter [count]="initialCount" 
    (change)="countChange($event)"></exe-counter>
  `
})
export class AppComponent {
  initialCount: number = 5;

  changeMsg: string;

  countChange(event: number) {
    this.changeMsg = `子元件change事件已觸發,目前值是: ${event}`;
  }
}
           

以上代碼運作後浏覽器顯示的結果:

Angular 2 Output

@Output('bindingPropertyName')

Output 裝飾器支援一個可選的參數,用來指定元件綁定屬性的名稱。如果沒有指定,則預設使用 @Output 裝飾器,裝飾的屬性名。具體示例如下:

counter.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
    selector: 'exe-counter',
    template: `
      <p>目前值: {{ count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    `
})
export class CounterComponent {
    @Input() count: number = 0;

    @Output('countChange') change: EventEmitter<number> = new EventEmitter<number>();
    ... // 其餘代碼未改變
}
           

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'exe-app',
  template: `
   <p>{{changeMsg}}</p> 
   <exe-counter [count]="initialCount" 
    (countChange)="countChange($event)"></exe-counter>
  `
})
export class AppComponent {
  initialCount: number = 5;

  changeMsg: string;

  countChange(event: number) {
    this.changeMsg = `子元件change事件已觸發,目前值是: ${event}`;
  }
}
           

@Component() - outputs

counter.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
    selector: 'exe-counter',
    template: `
      <p>目前值: {{ count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    `,
    outputs: ['change:countChange']
})
export class CounterComponent {
    @Input() count: number = 0;

    change: EventEmitter<number> = new EventEmitter<number>();

    increment() {
        this.count++;
        this.change.emit(this.count);
    }

    decrement() {
        this.count--;
        this.change.emit(this.count);
    }
}
           

Two-Way Data Binding

在介紹雙向綁定之前,我們先來說個需求:即在 CounterComponent 子元件 count 值發生變化的時候,需同步更新 AppComponent 父元件中的 initialCount 的值。通過上面的執行個體,我們知道我們可以在 AppComponent 父元件中監聽 CounterComponent 子元件的 change 事件,然後在 change 事件中更新 initialCount 的值。具體示例如下:

counter.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
    selector: 'exe-counter',
    template: `
      <p>子元件目前值: {{ count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    `
})
export class CounterComponent {
    @Input() count: number = 0;

    @Output() change: EventEmitter<number> = new EventEmitter<number>();

    increment() {
        this.count++;
        this.change.emit(this.count);
    }

    decrement() {
        this.count--;
        this.change.emit(this.count);
    }
}
           

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'exe-app',
  template: `
   <p>父元件目前值:{{ initialCount }}</p> 
   <exe-counter [count]="initialCount" 
    (change)="initialCount = $event"></exe-counter>
  `
})
export class AppComponent {
  initialCount: number = 5;
}
           

以上代碼運作後浏覽器顯示的結果:

Angular 2 Output

接下來我們來介紹雙向綁定,首先先看一下下圖:

Angular 2 Output

雙向綁定是由兩個單向綁定組成:

  • 模型 -> 視圖資料綁定
  • 視圖 -> 模型事件綁定

Angular 2 中

[]

實作了模型到視圖的資料綁定,

()

實作了視圖到模型的事件綁定。兩個結合在一起

[()]

就實作了雙向綁定。也被稱為

banana in the box

文法:

Angular 2 Output

[()] 文法示例

counter.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
    selector: 'exe-counter',
    template: `
      <p>子元件目前值: {{ count }}</p>
      <button (click)="increment()"> + </button>
      <button (click)="decrement()"> - </button>
    `
})
export class CounterComponent {
    @Input() count: number = 0;

    // 輸出屬性名稱變更: change -> countChange
    @Output() countChange: EventEmitter<number> = new EventEmitter<number>();
    ... // 其餘代碼未改變
}
           

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'exe-app',
  template: `
   <p>父元件目前值:{{ initialCount }}</p> 
   <exe-counter [(count)]="initialCount"></exe-counter>
  `
})
export class AppComponent {
  initialCount: number = 5;
}
           

從上面可以看出,

[(modelName)]

可以拆分成兩部分

modelName

modelNameChange

[modelName]

用于綁定輸入屬性,

(modelNameChange)

用于綁定輸出屬性。當 Angular 在解析模闆時,遇到

[(modelName)]

形式的綁定文法,它會期待這個指令中會存在一個名為

modelName

的輸入屬性和一個名為

modelNameChange

的輸出屬性。

ngModel

使用過 Angular 1.x 的讀者,應該很熟悉

ng-model

這個指令,我們通過它來實作資料的雙向綁定。那麼在 Angular 2 中有對應的指令麼 ?答案是有滴,它就是 ngModel 指令。接下來我們來馬上實戰一下:

ngModel雙向綁定示例

import { Component } from '@angular/core';

@Component({
  selector: 'exe-app',
  template: `
   <p>你輸入的使用者名是:{{ username }}</p> 
   <input type="text" [(ngModel)]="username" />
   `
})
export class AppComponent {
  username: string = '';
}
           

ngModel表單驗證示例

import { Component } from '@angular/core';

@Component({
  selector: 'exe-app',
  styles:[
    `.error { border: 1px solid red;}`
  ],
  template: `
   <p>你輸入的使用者名是:{{ username }}</p>
   <input type="text" 
      [(ngModel)]="username" 
      #nameModel="ngModel" 
      [ngClass]="{error: nameModel.invalid}"
      required/>
   {{nameModel.errors | json}}
   `
})
export class AppComponent {
  username: string = '';
}
           

以上示例利用

@Directive

指令 metadata 資訊中的

exportAs

屬性,擷取 ngModel 執行個體,進行擷取控件的狀态,控件狀态分類如下:

  • valid - 表單值有效
  • pristine - 表單值未改變
  • dirty - 表單值已改變
  • touched - 表單已被通路過
  • untouched - 表單未被通路過

我有話說

1.ngModel指令定義

@Directive({
  selector: '[ngModel]:not([formControlName]):not([formControl])',
  providers: [formControlBinding],
  exportAs: 'ngModel'
})
export class NgModel extends NgControl implements OnChanges,
  OnDestroy { ... }
           

2.不能同時使用 @Output 裝飾器 或在 @Directive、@Component outputs 字段中定義同一個輸入屬性,具體示例如下:

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
    selector: 'exe-counter',
    outputs: ['change:countChange']
})
export class CounterComponent {
    @Output('countChange') change: EventEmitter<number> = new EventEmitter<number>();
}
           

[email protected] vs outputs

相同點:

  • 它們都是用來定義輸出屬性

異同點:

  • outputs 定義在指令的 metadata 資訊中,開發者對指令的輸入屬性一目了然。此外對于未選用 TypeScript 作為開發語言的開發者,也隻能在 metadata 中定義指令的輸入屬性。
  • @Output 屬于屬性裝飾器,通過它我們可以一起定義屬性的通路描述符 (public、private、protected):
@Output('countChange') change: EventEmitter<number> = new EventEmitter<number>();
           

4.輸入、輸出屬性風格指南

4.1 堅持使用 @Input 和 @Output ,而非 @Directive 和 @Component 裝飾器的 inputs 和 outputs 屬性:

  • 易于在類裡面識别哪些屬性是輸入屬性或輸出屬性。

4.2 堅持把 @Input 或者 @Output 放到所裝飾的屬性的同一行:

  • 如果需要重命名與

    @Input

    或者

    @Output

    關聯的屬性或事件名,你可以在一個位置修改。

4.3 避免為輸入和輸出屬性指定别名

  • 同一個屬性有兩個名字(一個對内一個對外)很容易導緻混淆。

詳細資訊請參考 - Angular 2 風格指南 - STYLE 05-12

5.父元件什麼時候訂閱子元件

EventEmitter

類型的輸出屬性 ?

AppComponent/component.ngfactory.js

Angular 2 Output

CounterComponent/wrapper.ngfactory.js

Angular 2 Output

繼續閱讀