在Angular應用開發中,組件能夠說是隨處可見的。本篇文章將介紹幾種常見的組件通信場景,也就是讓兩個或多個組件之間交互的方法。javascript
根據數據的傳遞方向,分爲父組件向子組件傳遞、子組件向父組件傳遞及經過服務傳遞三種交互方法。html
子組件經過@Input裝飾器定義輸入屬性,而後父組件在引用子組件的時候經過這些輸入屬性向子組件傳遞數據,子組件可經過setter
或ngOnChanges()
來截聽輸入屬性值的變化。java
先定義兩個組件,分別爲子組件DemoChildComponent
和父組件DemoParentComponent
.api
子組件:數組
@Component({ selector: 'demo-child', template: ` <p>{{paramOne}}</p> <p>{{paramTwo}}</p> ` }) export class DemoChildComponent { @Input() paramOne: any; // 輸入屬性1 @Input() paramTwo: any; // 輸入屬性2 }
子組件經過@Input()
定義輸入屬性paramOne
和paramTwo
(屬性值能夠爲任意數據類型)框架
父組件:ide
@Component({ selector: 'demo-parent', template: ` <demo-child [paramOne]='paramOneVal' [paramTwo]='paramTwoVal'></demo-child> ` }) export class DemoParentComponent { paramOneVal: any = '傳遞給paramOne的數據'; paramTwoVal: any = '傳遞給paramTwo的數據'; }
父組件在其模板中經過選擇器demo-child
引用子組件DemoChildComponent
,並經過子組件的兩個輸入屬性paramOne
和paramTwo
向子組件傳遞數據,最後在子組件的模板中就顯示傳遞給paramOne的數據
和傳遞給paramTwo的數據
這兩行文本。函數
在實際應用中,咱們每每須要在某個輸入屬性值發生變化的時候作相應的操做,那麼此時咱們須要用到輸入屬性的 setter 來截聽輸入屬性值的變化。ui
咱們將子組件DemoChildComponent
進行以下改造:this
@Component({ selector: 'demo-child', template: ` <p>{{paramOneVal}}</p> <p>{{paramTwo}}</p> ` }) export class DemoChildComponent { private paramOneVal: any; @Input() set paramOne (val: any) { // 輸入屬性1 this.paramOneVal = val; // dosomething }; get paramOne () { return this.paramOneVal; }; @Input() paramTwo: any; // 輸入屬性2 }
在上面的代碼中,咱們能夠看到經過paramOne
屬性的 setter 將攔截到的值val
賦值給內部私有屬性paramOneVal
,達到父組件傳遞數據給子組件的效果。固然,最重要的是,在 setter 裏面你能夠作更多的其它操做,程序的靈活性就更強了。
經過 setter 截聽輸入屬性值的變化
的方法只能對單個屬性值變化進行監視,若是須要監視多個、交互式輸入屬性的時候,這種方法就顯得力不從心了。而經過使用 OnChanges 生命週期鉤子接口的 ngOnChanges() 方法(當組件經過@Input
裝飾器顯式指定的那些變量的值變化時調用)就能夠實現同時監視多個輸入屬性值的變化。
在子組件DemoChildComponent
新增ngOnChanges
:
@Component({ selector: 'demo-child', template: ` <p>{{paramOneVal}}</p> <p>{{paramTwo}}</p> ` }) export class DemoChildComponent implements OnChanges { private paramOneVal: any; @Input() set paramOne (val: any) { // 輸入屬性1 this.paramOneVal = val; // dosomething }; get paramOne () { return this.paramOneVal; }; @Input() paramTwo: any; // 輸入屬性2 ngOnChanges(changes: {[propKey: string]: SimpleChange}) { for (let propName in changes) { // 遍歷changes let changedProp = changes[propName]; // propName是輸入屬性的變量名稱 let to = JSON.stringify(changedProp.currentValue); // 獲取輸入屬性當前值 if (changedProp.isFirstChange()) { // 判斷輸入屬性是否首次變化 console.log(`Initial value of ${propName} set to ${to}`); } else { let from = JSON.stringify(changedProp.previousValue); // 獲取輸入屬性先前值 console.log(`${propName} changed from ${from} to ${to}`); } } } }
新增的ngOnChanges
方法接收的參數changes
是以輸入屬性名稱爲鍵、值爲SimpleChange
的對象,SimpleChange對象含有當前輸入屬性是否第一次變化、先前值、當前值等屬性。所以在ngOnChanges
方法中經過遍歷changes
對象可監視多個輸入屬性值並進行相應的操做。
前面介紹的都是子組件經過@Input裝飾器
定義輸入屬性,這樣父組件可經過輸入屬性將數據傳遞給子組件。
固然,咱們能夠想到一種更主動的方法,那就是獲取到父組件實例,而後調用父組件的某個屬性或方法來獲取須要的數據。考慮到每一個組件的實例都會添加到注入器的容器裏,所以可經過依賴注入來找到父組件的示例。
子組件獲取父組件實例
相比於父組件獲取子組件實例
(直接經過模板變量
、@ViewChild
或@ViewChildren
獲取)要麻煩一些。
要在子組件中獲取父組件的實例,有兩種狀況:
已知父組件的類型
這種狀況能夠直接經過在構造函數中注入DemoParentComponent來獲取已知類型的父組件引用,代碼示例以下:
@Component({ selector: 'demo-child', template: ` <p>{{paramOne}}</p> <p>{{paramTwo}}</p> ` }) export class DemoChildComponent { paramOne: any; paramTwo: any; constructor(public demoParent: DemoParentComponent) { // 經過父組件實例demoParent獲取數據 this.paramOne = demoParent.paramOneVal; this.paramTwo = demoParent.paramTwoVal; } }
未知父組件的類型
一個組件多是多個組件的子組件,有時候沒法直接知道父組件的類型,在Angular中,可經過類—接口(Class-Interface)
的方式來查找,即讓父組件經過提供一個與類—接口
標識同名的別名來協助查找。
首先建立DemoParent抽象類,它只聲明瞭paramOneVal
和paramTwoVal
屬性,沒有實現(賦值),示例代碼以下:
export abstract class DemoParent { paramOneVal: any; paramTwoVal: any; }
而後在父組件DemoParentComponent
的providers
元數據中定義一個別名 Provider,用 useExisting 來注入父組件DemoParentComponent的實例,代碼示例以下:
@Component({ selector: 'demo-parent', template: ` <demo-child [paramOne]='paramOneVal' [paramTwo]='paramTwoVal'></demo-child> `, providers: [{provider: DemoParent, useExisting: DemoParentComponent}] }) export class DemoParentComponent implements DemoParent { paramOneVal: any = '傳遞給paramOne的數據'; paramTwoVal: any = '傳遞給paramTwo的數據'; }
而後在子組件中就可經過DemoParent這個標識找到父組件的示例了,示例代碼以下:
@Component({ selector: 'demo-child', template: ` <p>{{paramOne}}</p> <p>{{paramTwo}}</p> ` }) export class DemoChildComponent { paramOne: any; paramTwo: any; constructor(public demoParent: DemoParent) { // 經過父組件實例demoParent獲取數據 this.paramOne = demoParent.paramOneVal; this.paramTwo = demoParent.paramTwoVal; } }
依然先定義兩個組件,分別爲子組件DemoChildComponent
和父組件DemoParentComponent
.
子組件:
@Component({ selector: 'demo-child', template: ` <p>子組件DemoChildComponent</p> ` }) export class DemoChildComponent implements OnInit { readyInfo: string = '子組件DemoChildComponent初始化完成!'; @Output() ready: EventEmitter = new EventEmitter<any>(); // 輸出屬性 ngOnInit() { this.ready.emit(this.readyInfo); } }
父組件:
@Component({ selector: 'demo-parent', template: ` <demo-child (ready)="onReady($event)" #demoChild></demo-child> <p> <!-- 經過本地變量獲取readyInfo屬性,顯示:子組件DemoChildComponent初始化完成! --> readyInfo: {{demoChild.readyInfo}} </p> <p> <!-- 經過組件類獲取子組件示例,而後獲取readyInfo屬性,顯示:子組件DemoChildComponent初始化完成! --> readyInfo: {{demoChildComponent.readyInfo}} </p> ` }) export class DemoParentComponent implements AfterViewInit { // @ViewChild('demoChild') demoChildComponent: DemoChildComponent; // 經過模板別名獲取 @ViewChild(DemoChildComponent) demoChildComponent: DemoChildComponent; // 經過組件類型獲取 ngAfterViewInit() { console.log(this.demoChildComponent.readyInfo); // 打印結果:子組件DemoChildComponent初始化完成! } onReady(evt: any) { console.log(evt); // 打印結果:子組件DemoChildComponent初始化完成! } }
子組件暴露一個 EventEmitter 屬性,當事件發生時,子組件利用該屬性 emits(向上彈射)事件。父組件綁定到這個事件屬性,並在事件發生時做出迴應。
在上面定義好的子組件和父組件,咱們能夠看到:
子組件經過@Output()
定義輸出屬性ready
,而後在ngOnInit中利用ready屬性的 emits(向上彈射)事件。
父組件在其模板中經過選擇器demo-child
引用子組件DemoChildComponent
,並綁定了一個事件處理器(onReady()),用來響應子組件的事件($event)並打印出數據(onReady($event)中的$event是固定寫法,框架(Angular)把事件參數(用 $event 表示)傳給事件處理方法)。
父組件不能使用數據綁定來讀取子組件的屬性或調用子組件的方法。但能夠在父組件模板裏,新建一個本地變量來表明子組件,而後利用這個變量來讀取子組件的屬性和調用子組件的方法。
在上面定義好的子組件和父組件,咱們能夠看到:
父組件在模板demo-child
標籤上定義了一個demoChild
本地變量,而後在模板中獲取子組件的屬性:
<p> <!-- 獲取子組件的屬性readyInfo,顯示:子組件DemoChildComponent初始化完成! --> readyInfo: {{demoChild.readyInfo}} </p>
本地變量方法是個簡單便利的方法。可是它也有侷限性,由於父組件-子組件的鏈接必須所有在父組件的模板中進行。父組件自己的代碼對子組件沒有訪問權。
若是父組件的類須要讀取子組件的屬性值或調用子組件的方法,就不能使用本地變量方法。
當父組件類須要這種訪問時,能夠把子組件做爲 ViewChild,注入到父組件裏面。
在上面定義好的子組件和父組件,咱們能夠看到:
父組件在組件類中經過@ViewChild()
獲取到子組件的實例,而後就能夠在模板或者組件類中經過該實例獲取子組件的屬性:
<p> <!-- 經過組件類獲取子組件示例,而後獲取readyInfo屬性,顯示:子組件DemoChildComponent初始化完成! --> readyInfo: {{demoChildComponent.readyInfo}} </p>
ngAfterViewInit() { console.log(this.demoChildComponent.readyInfo); // 打印結果:子組件DemoChildComponent初始化完成! }
Angular的服務能夠在模塊注入或者組件注入(均經過providers
注入)。
在模塊中注入的服務在整個Angular應用均可以訪問(除惰性加載的模塊)。
在組件中注入的服務就只能該組件和其子組件進行訪問,這個組件子樹以外的組件將沒法訪問該服務或者與它們通信。
下面的示例就以在組件中注入的服務來進行父子組件之間的數據傳遞:
通信的服務:
@Injectable() export class CallService { info: string = '我是CallService的info'; }
父組件:
@Component({ selector: 'demo-parent', template: ` <demo-child></demo-child> <button (click)="changeInfo()">父組件改變info</button> <p> <!-- 顯示:我是CallService的info --> {{callService.info}} </p> `, providers: [CallService] }) export class DemoParentComponent { constructor(public callService: CallService) { console.log(callService.info); // 打印結果:我是CallService的info } changeInfo() { this.callService.info = '我是被父組件改變的CallService的info'; } }
子組件:
@Component({ selector: 'demo-child', template: ` <button (click)="changeInfo()">子組件改變info</button> ` }) export class DemoChildComponent { constructor(public callService: CallService) { console.log(callService.info); // 打印結果:我是CallService的info } changeInfo() { this.callService.info = '我是被子組件改變的CallService的info'; } }
上面的代碼中,咱們定義了一個CallService服務,在其內定義了info屬性,後面將分別在父子組件經過修改這個屬性的值達到父子組件互相傳遞數據的目的。
而後經過DemoParentComponent的providers
元數據數組提供CallService服務的實例,並經過構造函數分別注入到父子組件中。
此時,經過父組件改變info按鈕
或子組件改變info按鈕
在父組件或子組件中改變CallService服務的info屬性值,而後在頁面可看到改變以後對應的info屬性值。