詳解Angualr 元件間通訊

NO IMAGE

Angualr 元件間通訊

約定: 遵循Angular官方的說法,下文中的AngularJS代指1.x版本,Angular代指Angular2及以後的升級版本。

採用Angular(或者任意MV*)的前端框架開發單頁應用(SPA)時,我們都可能會遇見如下的場景:

A元件和B元件之前需要相互通訊,或是A路由狀態需要知道B路由狀態的資訊等等業務需求。

這個時候就需要設計到採用一套合理的通訊方案來解決資料同步,資料通訊的問題。

AngularJS 元件間的資料通訊

在AngularJS中,也就是Angular JS 1.x版本中,我們需要實現控制器間的通訊,有很多種方案,常見的有:

1. 採用 SharedService, 利用共享的公共服務來實現資料交換。  

AngularJS中的Service被設計成單例的,這為這一方案,提供來底層的實現可行性,這一方案也是被廣泛採用的。

2. 利用AngularJS提供的事件機制,$rootScope.$broadcast/ $scope.$emit 配合 $on 方法實現。

該方案的實現具備一定的侷限性,比如:$emit方法只能向上傳遞事件,而不能實現向下的事件傳播。但是進行合理的搭配組合已經基本夠用了。

3. 利用瀏覽器的SessionStorage或者LocalStorage進行資料交換。

由於瀏覽器提供的這兩類本地儲存方案都提供了相應的storage事件,所以我們也可以使用該方案進行資料交換。使用該方案是應該注意及時清理無關資料。

4. 採用響應式的程式設計思想或者觀察者模式的應用。關於這一類實現,需要經歷一個程式設計思想的轉變,之後會通過專門的文章進行記錄。

5. 自身實現共享變數池。這個難度比較大,沒有一定的設計能力並不推薦。

由於AngularJS並不是本文的重點,所以這裡只簡單的提一下。後面介紹的Angular的方案也有許多可以通用的地方。

Angular 元件間的資料通訊

SharedService

共享服務方案在新的Angular中依然可以使用,而且無需額外的學習成本。這裡在之前的學習筆記裡有記錄,不再紀錄了。

SharedService 搭配 RxJS

聽說 SharedService 和 RxJS 搭配更實用哦!這裡的學習成本在於 RxJS ,RxJS只是 Rx思想的JS實現。這裡強烈推薦學習Rx程式設計思想, 她的學習收益比絕對讓你想象不到。

RxJS不需要我們不斷的手動去SharedService中檢查資料是否產生了變更,而是在資料有變化時,主動將變動的資料推送給感興趣的任何訂閱者。

舉個栗子:

我們有一份隨時可能會發生變動的資料存在了服務A中,在沒有使用RxJS(或是類似框架/庫)的情況下,我們想要知道資料的變化, 我們可能會採用輪詢的機制去不斷的詢問服務A,我們關心的資料是否發生了變化,如果變化了就做出相應的動作。我們處於一種盲目的主動狀態。

更高階一點的實現,就是我們可能把服務A實現稱為一個可被觀察的物件,這樣我們便能通過觀察服務A的狀態來獲取資料的變動。

現在我們來使用RxJS實現,RxJS現在可以理解為更高階的觀察者模式實現。當使用來RxJS之後,在服務A中的資料發生變化時,服務A會主動 將變動的資料推送給每一個感興趣的‘消費者’,這些消費者處於被動接受資料,自主處理資料的狀態中。RxJS不同於普通觀察者模式的地方在於, 它提供來一系列的資料操作方法,比如:filter, map等等。這樣就便於我們更加精細的處理資料。

下面通過一段簡單的示例程式碼來感受一下:


import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class DataService {

 private data: any;
 private subject: Subject<any> = new Subject<any>();

 setData(data: any): void {
 this.data = data;
 this.subject.next(data);
 }

 getData(): Observable<any> {
 return this.subject.asObservable();
 }
}

上面的程式碼中,我們簡單的實現了一個可觀察的資料服務,下面我們來使用這個服務


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

import { DataService } from './shared/Dataservice';

@Component({
 moduleId: module.id,
 selector: '<my-component></my-component>',
 templateUrl: '.name.component.html',
 providers: [DataService]
})
export class MyComponent implements OnInit {

 constructor(private dataService: DataService) {}

 ngOnInit() {
 // Will invoke data handler everytime
 // when other component use the setter this.dataService.setData()
 this.dataService.getData().subscribe(data => console.log(data));
 }
}

使用Angular底層提供的 @Input 和 @Output 裝飾器來實現元件間的通訊服務

新的Angular採用Web Component的方式進行元件的封裝,並將元件間的組織關係設計稱為樹狀結構。這為應用資料的流向,管理提供了良好的支援, 也使得我們可以在Angular應用中使用一些別的庫,比如: Redux 思想。基於這些設計理念,Angular為指令提供了更為強大的功能,元件也是指令。

採用 @Input 修飾屬性,實現 parent -> child 元件間的通訊

下面的示例程式碼將展示如何設定一個元件的Input屬性


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

@Component({
 moduleId: module.id,
 selector: 'child-component',
 template: `I'm {{ name }}`
})
export class ChildComponent implements OnInit {

 @Input() name: string;

 constructor() { }

 ngOnInit() { }
}

上面的程式碼中,我們實現了一個名叫ChildComponent的元件,這個元件的有一個採用@Input裝飾器修飾的屬性:name。

下面我們將展示如何使用這個這個元件,併為這個Input屬性賦值。


import { Component, OnInit } from '@angular/core';
import { ChildComponent } from './child-component';

@Component({
 moduleId: module.id,
 selector: 'parent-component',
 template: `<child-component [name]="childName"></child-component>`,
 // This is unnecessary when installing ChildComponent within Root NgModule
 directives: [ChildComponent]
})
export class ParentComponent implements OnInit {

 private childName: string;

 constructor() { }

 ngOnInit() { 
 this.childName = 'StevenShen';
 }
}

上面的程式碼實現中,在父元件中,我們為子元件的Input屬性設定了父元件中的特定值。關鍵點在如下片段:


<child-component [name]="childName"></child-component>

Angular在進行AOT操作時,會將特定的值注入給ChildComponent中。

如果你在CodePen,或是自己的本地實驗上面的程式碼你會發現,和AngularJS的指令中採用’@’, ‘=’, ‘&’等修飾的屬性不一樣的地方。

當父元件中的childName發生變化時,ChildComponent中的name屬性並沒有感知到變化。這是怎麼了,是不是感覺新版的Angular在 和我們開玩笑,wtf!!!內心的表情是這樣的 ○| ̄|_ 。(感覺一篇學習筆記開始被寫的畫風突變了。。。)

將父元件的屬性變化對映到子元件中

上一小節的實現,雖然在初始化子元件時,我們可以將父元件的值反饋到子元件中。但是,初始化完成後,父元件中相關屬性的變化卻不能被子元件感知。

這無疑是讓我們內心崩潰的。為什麼和AngularJS不一樣了???別急,下面我們將來提供解決方案。

利用Angular提供的元件生命週期鉤子函式ngOnChanges來監聽輸入屬性值的變化

需要實現讓子元件感知到父元件中相關屬性的變化,我們需要對Angualr元件的生命週期有一定的瞭解,採用Angular提供的元件生命週期的鉤子函式, 進行元件間資料的同步。(關於Angualr元件的生命週期,之後會有相關的學習筆記整理。到時候在加上鍊接。)這裡直接上程式碼:


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

@Component({
 moduleId: module.id,
 selector: 'child-component',
 template: `I'm {{ name }}`
})
export class ChildComponent {

 @Input() name: string;

 ngOnChanges(changes: SimpleChanges) {
 this.name = changes['childName'].currentValue;
 }
}

採用ES5中的getter和setter方法進行輸入屬性的監聽

在ES5中,我們在定義一個物件的屬性時,可以通過Object.defineProperty方法為一個物件的屬性設定關聯的getter和setter方法, 當我們進行這一操作後,以後該物件上的相關屬性的讀取和賦值操作,都會呼叫相應的getter/setter方法進行預處理或改變操作結果。

同樣的道理,在Angular中,我們通過設定和使用一個輸入屬性的setter方法,便可以攔截到父元件中相關值的變化,並進行特定的資料處理。

這種方法比較直觀,直接上程式碼:

父元件的程式碼實現:


import { Component } from '@angular/core';
@Component({
 moduleId: module.id,
 selector: 'name-parent',
 template: `
 <h2>Master controls {{names.length}} names</h2>
 <name-child *ngFor="let name of names" [name]="name"></name-child>
 `
})
export class ParentComponent {
 names = ['StevenShen', ' ', ' lei '];
}

子元件的程式碼實現


import { Component, Input } from '@angular/core';
@Component({
 moduleId: module.id,
 selector: 'name-child',
 template: `<h3>"{{name}}"</h3>`
})
export class ChildComponent {
 name: string = 'default name';

 @Input()
 set name(name: string) {
 this.name = (name && name.trim()) || 'default name';
 }

 get name() { return this.name; }
}

採用 @Output 修飾屬性,實現 child -> parent 元件間的通訊

新版的 Angular 中,子元件和父元件間的通訊,採用來事件的機制。這樣的設計有助於元件的複用和程式碼的解耦;
我們不需要過多的關心元件的具體實現,我們只需要知道一個元件它接收哪些資料,併產生哪些輸出事件即可。

直接上程式碼直觀瞭解一下:


@Component({
 moduleId: module.id,
 selector: 'child-component',
 template: `I'm {{ name }}`
})
export class ChildComponent {

 @Input() name: string;

 @Output() say: EventEmitter<boolean> = new EventEmitter<boolean>();

 ngOnChanges(changes: SimpleChange) {
 this.name = changes['childName'].currentValue;
 }

 speak() {
 this.say.emit(true);
 }
}

子元件變更完成後,我們來變更父元件的程式碼實現。


import { Component, OnInit } from '@angular/core';
import { ChildComponent } from './child-component';

@Component({
 moduleId: module.id,
 selector: 'parent-component',
 template: `<child-component [name]="childName" (say)="isChildSpeak($event)"></child-component>`,
 // This is unnecessary when installing ChildComponent within Root NgModule
 directives: [ChildComponent]
})
export class ParentComponent implements OnInit {

 private childName: string;

 constructor() { }

 ngOnInit() { 
 this.childName = 'StevenShen';
 }

 isChildSpeak(isIt: boolean) {
 console.log('The child speak status is %s', isIt ? 'ture' : 'false');
 }
}

這樣一來就實現了父子元件間的通訊了。

但是這樣的實現存在一定的侷限性:父元件不能使用資料繫結來讀取子元件的屬性或呼叫子元件的方法.

通過 @ViewChild 獲取元件的控制器/模版進行元件間的通訊

除開使用 @Input 和 @Output 修飾器搭配Angular的生命週期鉤子函式進行元件間通訊。 我們還可以採用@ViewChild來進行不同元件間的通訊,而不僅僅侷限於父子元件間的通訊。同時,採用@ViewChild的方式, 我們可以獲得更為精細的元件控制許可權,比如在父元件中讀取子元件的屬性值或呼叫子元件的方法。我們依然採用上面的程式碼來進行改造。

對於ChildComponent元件的變更:


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

@Component({
 moduleId: module.id,
 selector: 'child-component',
 template: `I'm {{ name }}`
})
export class ChildComponent {
 public name: string;

 speak() {
 console.log('say something whitout EventEmitter');
 }
}

對於ParentComponent元件的變更:


import { Component, OnInit, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
import { ChildComponent } from './child-component.ts';

@Component({
 moduleId: module.id,
 selector: 'parent-component',
 // attention #childCmp tag
 template: `
 <child-component #childCmp></child-component>
 <button (click)="child.name = childName"></button>
 `,
 // This is unnecessary when installing ChildComponent within Root NgModule
 directives: [ ChildComponent ]
})

export class ParentComponent implements OnInit, AfterViewInit {

 @ViewChild('childCmp') childCmp: ElementRef;


 constructor() { }

 ngOnInit() { 
 this.childCmp.name = 'StevenShen';
 }

 ngAfterViewInit() {
 this.childCmp.speak();
 }
}

通過上面的程式碼改造,我們同樣可以實現不同元件間的通訊,而且這樣的元件通訊已經不僅僅侷限於父子元件間的通訊了。

總結

由於技術水平和時間原因,這篇文章完成得比較粗略。主要整理的都是自己在工作中實際使用到的一些方案。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援指令碼之家。

您可能感興趣的文章:

AngualrJS中的Directive製作一個選單AngualrJS中每次$http請求時的一個遮罩層Directive