在Angular應用開發中,元件可以說是隨處可見的。本篇文章將介紹幾種常見的元件通訊場景,也就是讓兩個或多個元件之間互動的方法。
根據資料的傳遞方向,分為父元件向子元件傳遞、子元件向父元件傳遞及透過服務傳遞三種互動方法。
子元件透過@Input裝飾器定義輸入屬性,然後父元件在引用子元件的時候透過這些輸入屬性向子組件傳遞數據,子組件可透過setter
或ngOnChanges()
來截聽輸入屬性值的變化。
先定義兩個元件,分別為子元件DemoChildComponent
和父元件DemoParentComponent
.
子元件:
@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
(屬性值可以為任意資料型別)
父元件:
@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 來截斷輸入屬性值的變化。
我們將子元件DemoChildComponent
進行如下改造:
@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
本地变量,然后在模板中获取子组件的属性:
e388a4556c0f65e1904146cc1a846bee 2e82f1e8d02a21d96f8be243a083025d readyInfo: {{demoChild.readyInfo}} 94b3e26ee717c64999d7867364b1b4a3
本地变量方法是个简单便利的方法。但是它也有局限性,因为父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的代码对子组件没有访问权。
如果父组件的类需要读取子组件的属性值或调用子组件的方法,就不能使用本地变量方法。
当父组件类需要这种访问时,可以把子组件作为 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属性值。
相关推荐:
Angular开发实践(三):剖析Angular Component
以上是Angular開發實踐(四):組件之間的交互的詳細內容。更多資訊請關注PHP中文網其他相關文章!