Angular組件之間的交互分為:
1.通過輸入型綁定把數據從父組件傳到子組件。
2.通過setter截聽輸入屬性值的變化
3.通過ngOnChanges()來截聽輸入屬性值的變化
4.父組件監聽子組件的事件
5.父組件與子組件通過本地變量互動
6.父組件調用@ViewChild()
7.父組件和子組件通過服務來通訊
這些在Angular的中文官方文檔都有,類似的博客一大堆,寫這篇博客的目的不是說把人家做過的東西再做一遍,而是把人家做過的東西轉化成自己的東西,這樣才是有用的。
在博客正式開始之前我們先介紹一下項目目錄
----------app.component.ts
----------app.component.html
----------app.component.css
----------app.module.ts
----------child(文件夾)
--------------child.component.ts
--------------child.component.html
--------------child.component.css
一、通過輸入型綁定把數據從父組件傳到子組件
這個通訊方法的操作很簡單,父組件發送自己的數據給子組件,子組件接收父組件傳遞過來的數據。
1.app.component.html
<child [childData]="appData"></child><!--childData是子組件的接收參數,appData是父組件傳遞的數據,整個數據傳遞過程的重點,橋梁-->
2.app.component.ts
import { Component } from '@angular/core';
import { ChildComponent } from './child/child.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
appData=['Apple','Banana','watermelon','pear'];
}
3.child.component.ts
import { Component,Input } from '@angular/core';//記得導入Input裝飾器
@Component({
selector: 'child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent {
title = 'child';
@Input() childData:any;//接收數據的接口
}
4.child.component.html
<ul> <li *ngFor="let children of childData; let num1 = index">{{num1}}--{{children}}</li> </ul> <ul> <li *ngFor="let children of childData; index as num2">{{num2}}--{{children}}</li><!--index的第二種寫法--> </ul>
我們第一個實驗將父組件的數組傳遞給子組件然后渲染出來,效果是這樣的
二、通過setter截聽輸入屬性值的變化
這種交互方式的書寫格式有些不太符合我們平時的書寫規范
1.app.component.html
<child *ngFor="let app of appData" [child]="app"></child>
2.app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
appData=['Apple','Banana',' ','pear']; //父組件數據
}
3.child.component.ts
import { Component,Input } from '@angular/core';
@Component({
selector: 'child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent {
title = 'child';
private _name = ''; //定義一個私有向量,來暫時儲存父組件傳遞過來的數據
@Input() set child(name: string) { //child是我們用來接收父組件傳遞過來的數據的一個變量,我們用setter來截取到這個變量,然后做一些修改,child這個變量名
//它是取決於父組件app.component.html里面的[child]="app",不能隨便改動,child的形參名字可以隨便修改
this._name = name.trim() || 'Oranges'; //會trim掉名字里的空格,並把空值替換成Oranges。
}
get child(): string{ //用get重新獲取重置過后的child變量,這個過程中_name這個是有變量就用到了
return this._name;
}
}
4.child.component.html
<p>{{child}}</p>
我們第二個實驗的效果是這樣的
三、通過ngOnChanges()來截聽輸入屬性值的變化
OnChanges是一個生命周期鈎子,一旦檢測到該組件(或指令)的輸入屬性發生了變化,Angular就會調用它的ngOnChanges()方法。
1.app.component.html
<table> <tr> <td>ID</td> <td><input type="text" [(ngModel)]="id"/></td> </tr> <tr> <td>PassWord</td> <td><input type="text" [(ngModel)]="password"/></td> <!--使用ngModel要記住在app.module.ts里面添加FormsModule--> </tr> <tr colspan="2"> <td><button (click)="reset()">Reset Log</button></td> </tr> </table> <div> <child [id]="id" [password]="password"></child> </div>
2.app.component.ts
import { Component,ViewChild } from '@angular/core';//這里使用了我們第六點將會用到的父組件調用@ViewChild(),第六點細講
import { ChildComponent } from './child/child.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
id:number;
password:number;
@ViewChild(ChildComponent) childView: ChildComponent;//此處先忽略不看
constructor() {
this.reset();//構造函數,有初始化id和password的功能
}
reset() {
this.id = 100001;
this.password = 123456;
if (this.childView) {
this.childView.reset();
}
}
}
3.child.component.ts
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnChanges {
title = 'child';
@Input() id: number;
@Input() password: number;
changeLog: string[] = []; //這個數組用來存放修改日志,修改一個input就會儲存相應的記錄
ngOnChanges(changes: SimpleChanges) {
for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);//修改前的數據
let prev = JSON.stringify(chng.previousValue);//修改后的數據
this.changeLog.push(`${propName}被修改了: 前一個數據 = ${cur}, 后一個數據 = ${prev}`);
}
}
reset() {
this.changeLog.length = 0; //清除日志
}
}
4.child.component.html
<div class="hero"> <h4>-- Change Log --</h4> <div *ngFor="let chg of changeLog">{{chg}}</div> <!--將日志一條一條打印出來--> </div>
我們第三個實驗的效果是這樣的
四、父組件監聽子組件的事件
通過@Output裝飾器自定義一個方法,在需要emits(向上彈射)的函數里面彈射變量
1.app.component.html
<p>{{title}}</p> <child (onClick)="onCli($event)"></child> <!--onClick是子組件自定義的事件,觸發onClick事件,執行父組件的onCli()方法-->
2.app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title="你還沒點擊按鈕";
onCli(isClick: boolean) { //父組件的方法,更換title的值
if(isClick){ //如果子組件彈射出來的變量為true
this.title="你點擊了按鈕"; //那么就更改title
}
}
}
3.child.component.ts
import { Component, EventEmitter, Output } from '@angular/core';//注意導入
@Component({
selector: 'child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent{
title = 'child';
@Output() onClick = new EventEmitter<boolean>();//暴露自定義事件onClick
click(isClick: boolean) { //click()只是一個普通的方法
this.onClick.emit(isClick);//向父組件彈射isClick變量,這個變量在button被點擊的時候觸發
}
}
4.child.component.html
<button (click)="click(true)">點擊</button> <!--給click方法傳入true-->
1.在子組件里面定義並暴露出自定義事件;
2.在子組件里面需要彈射的函數里面彈射變量;
3.在父組件里面通過$event接收變量。
我們第四個實驗的效果是這樣的
五、父組件與子組件通過本地變量互動
這種通訊比較簡單,而且父組件可以同時調用子組件的變量或方法1.app.component.html
<p>{{title}}</p><!--調用父組件自己的變量--> <p>{{data.title}}</p><!--調用子組件的變量--> <child #data></child><!--在父組件模板里,新建一個本地變量來代表子組件,然后利用這個變量來讀取子組件的屬性和調用子組件的方法--> <button (click)="data.click()">點擊</button><!--調用子組件的方法-->
2.app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title="app";//父組件的變量
}
3.child.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent{
title = 'child';//子組件的變量
click(){//子組件的方法
this.title="changeChild";
}
}
child.component.html不用編寫代碼
我們第五個實驗的效果是這樣的
六、父組件調用@ViewChild()
這種父子組件之間的通訊方式也很容易理解,項目代碼和第五個通訊方式差不多
1.app.component.html
<p>{{title}}</p> <p>{{data.title}}</p> <child #data></child> <button (click)="onclick()">點擊</button><!--注意這里,跟第五個方法有些區別-->
2.app.component.ts
import { Component } from '@angular/core';
import { ViewChild } from '@angular/core';
import { ChildComponent } from './child/child.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title="app";
@ViewChild(ChildComponent)//通過@ViewChild屬性裝飾器,將子組件CountdownTimerComponent注入到私有屬性timerComponent里面,此處不能加分號。
private child: ChildComponent;
onclick(){
this.child.click();
}
}
3.child.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent{
title = 'child';
click(){
this.title="changeChild";
}
}
child.component.html也是不用編寫代碼,我們第六個實驗的效果跟第五個是一樣的,這里不再講述。
七、父組件和子組件通過服務來通訊
這種服務通訊方式我在 一篇文章有詳細地解析過。這個實驗要在app.component.html加入新的文件Service.ts
1.app.component.html
<p>{{title}}</p> <button (click)="onclick()">點擊</button> <child></child>
2.app.component.ts
import { Component } from '@angular/core';
import { ChildComponent } from './child/child.component';
import { Service } from './Service';//導入服務
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title='app';
constructor(public pService: Service) { //原來的寫法是這樣的pService = new Service();
}
onclick(){
this.title = this.pService.getData();
console.log(this.title);
}
}
3.child.component.ts
import { Component } from '@angular/core';
import { Service } from '../Service';
@Component({
selector: 'child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent{
title = 'child';
constructor(public pService: Service) {
}
onclick(){
this.title = this.pService.getData();
}
}
4.child.component.html
<p>{{title}}</p> <button (click)="onclick()">點擊</button>
app.component.html和child.component.html,app.component.ts和child.component.ts的代碼幾乎一樣,目的是要展示出服務在父子組件甚至任何組件里面的使用
5.Service.ts
import {Injectable} from "@angular/core"
@Injectable()
export class Service {
title='service';//公共變量
getData() {//公共方法
return this.title;
}
}
6.app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ChildComponent } from './child/child.component';
import { Service } from './Service';//導入服務
@NgModule({
declarations: [
AppComponent,
ChildComponent
],
imports: [
BrowserModule
],
providers: [Service],//注冊服務
bootstrap: [AppComponent]
})
export class AppModule { }
實驗七的效果是這樣的,在點擊之前
點擊之后
八、結語
至此七種父子組件之間的交互方式解析完畢,大家在不同的代碼情況因地制宜,選擇最合適的方式!
