Angular2元件間互動
元件間互動簡單來說就是讓兩個或多個元件之間共享資訊。接下來我們就對Angular2元件間的互動做一個簡單的解釋。當然做好的文件還是官方文件:https://www.angular.cn/guide/component-interaction
一、通過@Input把父元件的屬性繫結到子元件
@Input註解是屬性繫結,通常在父元件需要向子元件傳遞資料的時候使用。關於@Input你可以簡單的理解為子元件建立的時候需要傳遞引數(當然子元件的建立指的是在父元件對應的html裡面申明)。
有@Input那肯定就會對應的有一個@Output,@Output是用於子元件向父元件傳遞資料的時候觸發事件。關於@Output我們會在下面講到。
@Input的使用簡單的很,首先在子元件定義的時候我們先明確哪些屬性是需要父元件傳遞過來的,給加上@Input註解就完事了。然後父元件通過模板語法把屬性繫結到子元件上去就完事了。
我們用一個非常簡單的例項看下@Input的使用,父元件需要把Hero物件傳遞到子元件裡面去。
子元件hero屬性加上@Input()註解。
import {Component, Input} from '@angular/core';
import {Hero} from '../hero';
@Component({
selector: 'app-data-child',
template: `
<p>我是子元件,父元件傳遞的值是:"{{hero.name}}"</p>
`
})
export class DataChildComponent {
// 該屬性需要從父元件傳遞過來
@Input() hero: Hero;
constructor() { }
}
父元件通過[hero]=”parentHero”把parentHero屬性繫結到子元件上去
import {Component} from '@angular/core';
import {Hero} from '../hero';
@Component({
selector: 'app-data-parent',
styleUrls: ['./data-parent.component.css'],
template: `
<app-data-child [hero]="parentHero"></app-data-child>
`
})
export class DataParentComponent {
parentHero: Hero = new Hero();
constructor() {
this.parentHero.name = '我是父元件定義的';
}
}
@Input的使用就是這麼的簡單的,除了[hero]=”parentHero”單向繫結,我們也可以使用[(hero)]=”parentHero”雙向繫結。
1.1、通過setter截聽輸入屬性值的變化
使用輸入屬性的 setter、getter方法來攔截父元件中值的變化,一邊是在setter函式裡面做一些相應的處理,然後getter函式裡面返回。 我們還是繼續在上面的基礎上做一個簡單的修改。對子元件做一個簡單的修改把父元件傳遞過來的Hero物件裡面的名字都改成大寫。
import {Component, Input} from '@angular/core';
import {Hero} from '../hero';
@Component({
selector: 'app-data-child',
template: `
<p>我是子元件,父元件傳遞的值是:"{{hero.name}}"</p>
`
})
export class DataChildComponent {
private _hero: Hero;
// 該屬性需要從父元件傳遞過來,我們把Hero物件裡面的name改成大寫
@Input()
set hero(hero: Hero) {
// 把父元件傳遞過來的資料裝換成大寫
const name = (hero.name && hero.name.toUpperCase()) || '<no name set>';
this._hero = new Hero();
this._hero.name = name;
}
get hero(): Hero {
return this._hero;
}
constructor() {
}
}
1.2、通過ngOnChanges()鉤子來攔截輸入屬性值的變化
使用OnChanges生命週期鉤子介面的ngOnChanges() 方法來監測輸入屬性值的變化並做出迴應。當輸入屬性值變化的時候會回撥ngOnChanges()方法。
ngOnChanges()鉤子:當Angular(重新)設定資料繫結輸入屬性時響應。 該方法接受當前和上一屬性值的SimpleChanges物件
當被繫結的輸入屬性的值發生變化時呼叫,首次呼叫一定會發生在ngOnInit()之前。
關於通過ngOnChanges()鉤子來攔截屬性值的變化。想強調的一點就是。資料變化指的是屬性指向的地址發生改變才會回撥ngOnChanges()函式。所以對於一些自定義的資料型別(class)要特別小心。
我們還是用一個簡單的例子來說明怎麼通過通過ngOnChanges()鉤子來攔截輸入屬性值的變化。子元件需要父元件傳遞一個string屬性過來,通過ngOnChanges()鉤子攔截到屬性的變化,把資料都改為大寫的。
子元件,把父元件傳遞過來的資料改為大寫
import {Component, Input, OnChanges, SimpleChange} from '@angular/core';
@Component({
selector: 'app-data-child',
template: `
<p>我是子元件,父元件傳遞的值是:"{{inputString}}"</p>
`
})
export class DataChildComponent implements OnChanges {
// 該屬性需要從父元件傳遞過來
@Input()
inputString: string;
// 攔截inputString的變化,並且把他變成大寫
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
for (const propName in changes) {
if (changes.hasOwnProperty(propName)) {
const changedProp = changes[propName];
const to = JSON.stringify(changedProp.currentValue);
// 我們這裡只想要inputString屬性的變化
if (propName === 'inputString') {
if (changedProp.isFirstChange()) {
// 第一次資料設定
} else {
// 不是第一次
}
this.inputString = to.toUpperCase();
}
}
}
}
}
父元件相關程式碼
import {Component} from '@angular/core';
@Component({
selector: 'app-data-parent',
styleUrls: ['./data-parent.component.css'],
template: `
<app-data-child [(inputString)]="inputString"></app-data-child>
<button (click)="onValueChangeClick()">改變值</button>
`
})
export class DataParentComponent {
inputString: string;
constructor() {
this.inputString = 'nihao';
}
onValueChangeClick() {
this.inputString = 'change';
}
}
二、通過@Output讓父元件監聽子元件的事件
通過@Output註解指明一個輸出屬性(EventEmitter型別)。通過在子元件裡面暴露一個EventEmitter屬性,當事件發生時,子元件利用該屬性 emits(向上彈射)事件。父元件繫結到這個事件屬性,並在事件發生時作出迴應。子元件的EventEmitter屬性是一個輸出屬性,所以需要帶有@Output裝飾器。這一部分可以類比JAVA裡面的介面的使用。相當於在父元件裡面實現介面,在子元件裡面呼叫介面。
通過@Output讓父元件監聽子元件的事件的使用也非常簡單。我們分為三個步驟:
- 子元件裡面明確我們要把什麼事件丟擲去給父元件(定義一個@Output()註解修飾的EventEmitter屬性),
- 丟擲事件。子元件做了什麼動作之後丟擲事件。呼叫EventEmitter屬性的emit()方法丟擲事件。
- 父元件裡面繫結事件處理器,當子元件有事件丟擲來的時候會呼叫父元件的處理函式。
我還是以一個非常簡單的例子來說明,我們在子元件裡面定義一個button,當button點選的時候。把事件拋給父元件。統計點選的次數。
子元件相關程式碼
import {Component, EventEmitter, OnChanges, Output} from '@angular/core';
@Component({
selector: 'app-data-child',
template: `
<button (click)="vote(true)">點選</button>
`
})
export class DataChildComponent {
// @Output定義一個準備回撥父元件的事件EventEmitter也是可以傳遞引數的
@Output() voted = new EventEmitter<boolean>();
vote(agreed: boolean) {
// 把事件往上丟擲去,可以帶引數
this.voted.emit(agreed);
}
}
父元件相關程式碼
import {Component} from '@angular/core';
@Component({
selector: 'app-data-parent',
styleUrls: ['./data-parent.component.css'],
template: `
<p>點選 {{clickCount}} 次</p>
<app-data-child (voted)="onVoted($event)"></app-data-child>
`
})
export class DataParentComponent {
clickCount = 0;
/**
* 子元件拋上來的事件
*/
onVoted(agreed: boolean) {
this.clickCount++;
}
}
三、父子元件通過本地變數互動
在父元件模板裡,新建一個本地變數來代表子元件(指向子元件)。然後利用這個變數來讀取子元件的屬性和呼叫子元件的方法。
一個本地變數互動的簡單例項,在父元件裡面有兩個按鈕:一個開始按鈕、一個結束按鈕。呼叫子元件裡面的開始和結束方法。在下面程式碼中chiild指向的就是子元件,然後通過chiild來呼叫子元件裡面的方法。
子元件相關程式碼
import {Component} from '@angular/core';
@Component({
selector: 'app-data-child',
template: `<p>{{message}}</p>`,
styleUrls: ['./data-child.component.css']
})
export class DataChildComponent {
message = '初始值';
onStart(): void {
this.message = '父元件告訴開始了';
}
onEnd(): void {
this.message = '父元件告訴結束了';
}
}
父元件相關程式碼
import {Component} from '@angular/core';
@Component({
selector: 'app-data-parent',
template: `
<button (click)="chiild.onStart()">開始</button>
<button (click)="chiild.onEnd()">結束</button>
<app-data-child #chiild></app-data-child>
`,
styleUrls: ['./data-parent.component.css']
})
export class DataParentComponent {
}
父子元件通過本地變數互動缺點是,本地變數的作用範圍只是html(模板)檔案裡面。在ts檔案裡面沒辦法使用。並且只能是單向的,只能在父元件的模板裡面呼叫子元件的屬性或者方法。
四、父元件通過@ViewChild()呼叫子元件裡面的屬性方法
父子元件通過本地變數互動的缺點是變數只能在模板裡面使用,沒辦法在ts檔案程式碼裡面使用。@ViewChild()就是來解決這個辦法的。
當父元件類需要訪問子元件屬性或者方法的時候,可以把子元件作為 ViewChild,注入到父元件裡面。
我們還是對上面的例子做一個簡單的修改,父元件告訴子元件開始和結束。
子元件程式碼
import {Component} from '@angular/core';
@Component({
selector: 'app-data-child',
template: `<p>{{message}}</p>`,
styleUrls: ['./data-child.component.css']
})
export class DataChildComponent {
message = '初始值';
onStart(): void {
this.message = '父元件告訴開始了';
}
onEnd(): void {
this.message = '父元件告訴結束了';
}
}
父元件程式碼
import {Component, ViewChild} from '@angular/core';
import {DataChildComponent} from './data-child.component';
@Component({
selector: 'app-data-parent',
template: `
<button (click)="start()">開始</button>
<button (click)="end()">結束</button>
<app-data-child #chiild></app-data-child>
`,
styleUrls: ['./data-parent.component.css']
})
export class DataParentComponent {
@ViewChild(DataChildComponent)
private childComponent: DataChildComponent;
start(): void {
this.childComponent.onStart();
}
end(): void {
this.childComponent.onEnd();
}
}
當在一個父元件裡面有同一個子元件多個的時候,又應該怎麼處理呢。
import {Component, ViewChild} from '@angular/core';
import {DataChildComponent} from './data-child.component';
@Component({
selector: 'app-data-parent',
template: `
<button (click)="start()">開始</button>
<button (click)="end()">結束</button>
<app-data-child #chiild1></app-data-child>
<app-data-child #chiild2></app-data-child>
`,
styleUrls: ['./data-parent.component.css']
})
export class DataParentComponent {
@ViewChild('chiild1')
private childComponent1: DataChildComponent;
@ViewChild('chiild2')
private childComponent2: DataChildComponent;
start(): void {
this.childComponent1.onStart();
this.childComponent2.onStart();
}
end(): void {
this.childComponent1.onEnd();
this.childComponent2.onEnd();
}
}
五、父子元件通過服務通訊
父元件和它的子元件共享同一個服務(父元件和子元件使用的是同一個服務例項,說白了就是同一個物件)。父子元件間通過釋出訂閱的訊息機制來實現通訊,一個釋出訊息,一個訂閱訊息。說白了就是觀察值模式。
我們用一個父子元件的相互通訊來做一個簡單的說明。MissionService就是中間服務,MissionService裡面有兩個bservable屬性。用來發布訂閱訊息的。在一個元件裡面釋出另一個元件裡面訂閱。
service->MissionService
import {Injectable} from '@angular/core';
import {Subject} from 'rxjs';
@Injectable()
export class MissionService {
// Subject可以看著是一個橋樑或者代理
private childToParentSubject = new Subject<string>();
private parentToChildSubject = new Subject<string>();
// 定義觀察者(Observable變數在定義的時候都會在後面加上$)
childToParentObservable$ = this.childToParentSubject.asObservable();
parentToChildObservable$ = this.parentToChildSubject.asObservable();
// 父元件給子元件傳送訊息,這樣parentToChildObservable$就能收到訊息
parentSendMessageToChild(mission: string) {
this.parentToChildSubject.next(mission);
}
// 子元件給父元件傳送訊息,這樣childToParentObservable$就能收到訊息
childSendMessageToParent(astronaut: string) {
this.childToParentSubject.next(astronaut);
}
}
子元件對應程式碼
import {Component, OnDestroy} from '@angular/core';
import {MissionService} from './mission.service';
import {Subscription} from 'rxjs';
@Component({
selector: 'app-service-child',
template: `
<p>收到父元件的訊息: {{message}}</p>
<button (click)="sendMessage()">傳送訊息</button>
`,
styleUrls: ['./service-child.component.css']
})
export class ServiceChildComponent implements OnDestroy {
message = '';
subscription: Subscription;
constructor(private missionService: MissionService) {
// 訂閱訊息
this.subscription = missionService.parentToChildObservable$.subscribe(
mission => {
this.message = mission;
});
}
// 傳送訊息
sendMessage() {
this.missionService.childSendMessageToParent('我是子元件給你發訊息了哈');
}
ngOnDestroy() {
// 元件銷燬的時候,subscription需要取消訂閱
this.subscription.unsubscribe();
}
}
父元件對應程式碼
import {Component, OnDestroy} from '@angular/core';
import {MissionService} from './mission.service';
import {Subscription} from 'rxjs';
@Component({
selector: 'app-service-parent',
template: `
<p>收到子元件的訊息: {{message}}</p>
<button (click)="sendMessage()">傳送訊息</button>
<app-service-child></app-service-child>
`,
styleUrls: ['./service-parent.component.css'],
providers: [MissionService]
})
export class ServiceParentComponent implements OnDestroy{
message = '';
subscription: Subscription;
constructor(private missionService: MissionService) {
// 訂閱訊息,當資料改變的時候,會呼叫到改函式
this.subscription = missionService.childToParentObservable$.subscribe(
astronaut => {
this.message = astronaut;
});
}
sendMessage() {
this.missionService.parentSendMessageToChild('我是父元件給你發訊息了哈');
}
ngOnDestroy(): void {
// 取消訂閱
this.subscription.unsubscribe();
}
}
本文涉及到的所有例子下載地址:DEMO下載地址。demo裡面的例子可能會稍微複雜一點。
關於元件間的互動,我們就講這麼多,貌似看起來也不是很複雜,咱也是個初學者(我是做android,以前也沒有接觸過前段方面的知識)。這裡我想在說一句最好的文件還是官方文件,強烈推薦大家看官方文件 https://www.angular.cn/guide/component-interaction#parent-interacts-with-child-via-emlocal-variableem 。