? ? ? ?組件間交互簡單來說就是讓兩個或多個組件之間共享信息徊哑。接下來我們就對Angular2組件間的交互做一個簡單的解釋。當然做好的文檔還是官方文檔:https://www.angular.cn/guide/component-interaction
一议经、通過@Input把父組件的屬性綁定到子組件
? ? ? ?@Input注解是屬性綁定,通常在父組件需要向子組件傳遞數(shù)據(jù)的時候使用谴返。關(guān)于@Input你可以簡單的理解為子組件創(chuàng)建的時候需要傳遞參數(shù)(當然子組件的創(chuàng)建指的是在父組件對應的html里面申明)煞肾。
有@Input那肯定就會對應的有一個@Output,@Output是用于子組件向父組件傳遞數(shù)據(jù)的時候觸發(fā)事件嗓袱。關(guān)于@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函數(shù)里面做一些相應的處理伴郁,然后getter函數(shù)里面返回。 我們還是繼續(xù)在上面的基礎上做一個簡單的修改蛋叼。對子組件做一個簡單的修改把父組件傳遞過來的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) {
// 把父組件傳遞過來的數(shù)據(jù)裝換成大寫
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() 方法來監(jiān)測輸入屬性值的變化并做出回應。當輸入屬性值變化的時候會回調(diào)ngOnChanges()方法歌馍。
ngOnChanges()鉤子:當Angular(重新)設置數(shù)據(jù)綁定輸入屬性時響應握巢。 該方法接受當前和上一屬性值的SimpleChanges對象
當被綁定的輸入屬性的值發(fā)生變化時調(diào)用,首次調(diào)用一定會發(fā)生在ngOnInit()之前松却。
? ? ? ?關(guān)于通過ngOnChanges()鉤子來攔截屬性值的變化暴浦。想強調(diào)的一點就是溅话。數(shù)據(jù)變化指的是屬性指向的地址發(fā)生改變才會回調(diào)ngOnChanges()函數(shù)。所以對于一些自定義的數(shù)據(jù)類型(class)要特別小心歌焦。
? ? ? ?我們還是用一個簡單的例子來說明怎么通過通過ngOnChanges()鉤子來攔截輸入屬性值的變化飞几。子組件需要父組件傳遞一個string屬性過來,通過ngOnChanges()鉤子攔截到屬性的變化独撇,把數(shù)據(jù)都改為大寫的屑墨。
子組件,把父組件傳遞過來的數(shù)據(jù)改為大寫
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()) {
// 第一次數(shù)據(jù)設置
} else {
// 不是第一次
}
this.inputString = to.toUpperCase();
}
}
}
}
}
父組件相關(guān)代碼
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讓父組件監(jiān)聽子組件的事件
? ? ? ?通過@Output注解指明一個輸出屬性(EventEmitter類型)。通過在子組件里面暴露一個EventEmitter屬性搜立,當事件發(fā)生時程腹,子組件利用該屬性 emits(向上彈射)事件。父組件綁定到這個事件屬性儒拂,并在事件發(fā)生時作出回應寸潦。子組件的EventEmitter屬性是一個輸出屬性,所以需要帶有@Output裝飾器社痛。這一部分可以類比JAVA里面的接口的使用见转。相當于在父組件里面實現(xiàn)接口,在子組件里面調(diào)用接口蒜哀。
? ? ? ?通過@Output讓父組件監(jiān)聽子組件的事件的使用也非常簡單斩箫。我們分為三個步驟:
- 子組件里面明確我們要把什么事件拋出去給父組件(定義一個@Output()注解修飾的EventEmitter屬性),
- 拋出事件撵儿。子組件做了什么動作之后拋出事件乘客。調(diào)用EventEmitter屬性的emit()方法拋出事件。
- 父組件里面綁定事件處理器淀歇,當子組件有事件拋出來的時候會調(diào)用父組件的處理函數(shù)易核。
? ? ? ?我還是以一個非常簡單的例子來說明,我們在子組件里面定義一個button浪默,當button點擊的時候牡直。把事件拋給父組件。統(tǒng)計點擊的次數(shù)。
子組件相關(guān)代碼
import {Component, EventEmitter, OnChanges, Output} from '@angular/core';
@Component({
selector: 'app-data-child',
template: `
<button (click)="vote(true)">點擊</button>
`
})
export class DataChildComponent {
// @Output定義一個準備回調(diào)父組件的事件EventEmitter也是可以傳遞參數(shù)的
@Output() voted = new EventEmitter<boolean>();
vote(agreed: boolean) {
// 把事件往上拋出去,可以帶參數(shù)
this.voted.emit(agreed);
}
}
父組件相關(guān)代碼
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++;
}
}
三怔球、父子組件通過本地變量互動
? ? ? ?在父組件模板里,新建一個本地變量來代表子組件(指向子組件)踢步。然后利用這個變量來讀取子組件的屬性和調(diào)用子組件的方法。
? ? ? ?一個本地變量互動的簡單實例咙崎,在父組件里面有兩個按鈕:一個開始按鈕桩盲、一個結(jié)束按鈕胳喷。調(diào)用子組件里面的開始和結(jié)束方法湃番。在下面代碼中chiild指向的就是子組件,然后通過chiild來調(diào)用子組件里面的方法厌蔽。
子組件相關(guān)代碼
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 = '父組件告訴結(jié)束了';
}
}
父組件相關(guān)代碼
import {Component} from '@angular/core';
@Component({
selector: 'app-data-parent',
template: `
<button (click)="chiild.onStart()">開始</button>
<button (click)="chiild.onEnd()">結(jié)束</button>
<app-data-child #chiild></app-data-child>
`,
styleUrls: ['./data-parent.component.css']
})
export class DataParentComponent {
}
父子組件通過本地變量互動缺點是牵辣,本地變量的作用范圍只是html(模板)文件里面摔癣。在ts文件里面沒辦法使用奴饮。并且只能是單向的,只能在父組件的模板里面調(diào)用子組件的屬性或者方法择浊。
四戴卜、父組件通過@ViewChild()調(diào)用子組件里面的屬性方法
? ? ? ?父子組件通過本地變量互動的缺點是變量只能在模板里面使用,沒辦法在ts文件代碼里面使用琢岩。@ViewChild()就是來解決這個辦法的投剥。
當父組件類需要訪問子組件屬性或者方法的時候,可以把子組件作為 ViewChild担孔,注入到父組件里面江锨。
? ? ? ?我們還是對上面的例子做一個簡單的修改,父組件告訴子組件開始和結(jié)束糕篇。
子組件代碼
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 = '父組件告訴結(jié)束了';
}
}
父組件代碼
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()">結(jié)束</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()">結(jié)束</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();
}
}
五拌消、父子組件通過服務通訊
? ? ? ?父組件和它的子組件共享同一個服務(父組件和子組件使用的是同一個服務實例挑豌,說白了就是同一個對象)。父子組件間通過發(fā)布訂閱的消息機制來實現(xiàn)通訊墩崩,一個發(fā)布消息氓英,一個訂閱消息。說白了就是觀察值模式鹦筹。
? ? ? ?我們用一個父子組件的相互通信來做一個簡單的說明铝阐。MissionService就是中間服務,MissionService里面有兩個bservable屬性铐拐。用來發(fā)布訂閱消息的饰迹。在一個組件里面發(fā)布另一個組件里面訂閱。
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();
// 父組件給子組件發(fā)送消息余舶,這樣parentToChildObservable$就能收到消息
parentSendMessageToChild(mission: string) {
this.parentToChildSubject.next(mission);
}
// 子組件給父組件發(fā)送消息啊鸭,這樣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()">發(fā)送消息</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;
});
}
// 發(fā)送消息
sendMessage() {
this.missionService.childSendMessageToParent('我是子組件給你發(fā)消息了哈');
}
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()">發(fā)送消息</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) {
// 訂閱消息匿值,當數(shù)據(jù)改變的時候赠制,會調(diào)用到改函數(shù)
this.subscription = missionService.childToParentObservable$.subscribe(
astronaut => {
this.message = astronaut;
});
}
sendMessage() {
this.missionService.parentSendMessageToChild('我是父組件給你發(fā)消息了哈');
}
ngOnDestroy(): void {
// 取消訂閱
this.subscription.unsubscribe();
}
}
? ? ? ?本文涉及到的所有例子下載地址:DEMO下載地址。demo里面的例子可能會稍微復雜一點。
? ? ? ?關(guān)于組件間的交互钟些,我們就講這么多烟号,貌似看起來也不是很復雜,咱也是個初學者(我是做android,以前也沒有接觸過前段方面的知識)政恍。這里我想在說一句最好的文檔還是官方文檔汪拥,強烈推薦大家看官方文檔 https://www.angular.cn/guide/component-interaction#parent-interacts-with-child-via-emlocal-variableem 。