Output 是屬性裝飾器,用來定義組件內的輸出屬性。在 Angular 2 Input 文章中,咱們介紹了 Input 裝飾器的做用,也瞭解了當應用啓動時,Angular 會從根組件開始啓動,並解析整棵組件樹,數據由上而下流下下一級子組件。而咱們今天介紹的 Output 裝飾器,是用來實現子組件將信息經過事件的形式通知到父級組件。具體以下圖所示:html
在介紹 Output 屬性裝飾器前,咱們先來介紹一下 EventEmitter
這個幕後英雄:typescript
EventEmitter 用來觸發自定義事件,具體使用示例以下:json
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 對象。是否是感受有點抽象,咱們立刻實戰一下。segmentfault
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.tsapp
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}`; } }
以上代碼運行後瀏覽器顯示的結果:ide
Output 裝飾器支持一個可選的參數,用來指定組件綁定屬性的名稱。若是沒有指定,則默認使用 @Output 裝飾器,裝飾的屬性名。具體示例以下:ui
counter.component.tsthis
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.tsspa
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}`; } }
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); } }
在介紹雙向綁定以前,咱們先來講個需求:即在 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 中 []
實現了模型到視圖的數據綁定,()
實現了視圖到模型的事件綁定。兩個結合在一塊兒 [()]
就實現了雙向綁定。也被稱爲 banana in the box
語法:
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
的輸出屬性。
使用過 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>(); }
3.@Output 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
CounterComponent/wrapper.ngfactory.js