组件之间的交互
在实际的工作中,有时候会有这样的需要,在同一个页面,这个页面又可能是不止一个组件构成,可能需要两个,甚至多个组件时,做个组件还需要进行数据信息的共享。
用两个组件之间的通讯进行举例
1.创建两个组件,一个父组件,一个子组件。
ng g c parentCom
ng g c childCom
wujiayudeMacBook-Pro:demo-test wjy$ ng g c parentCom
CREATE src/app/parent-com/parent-com.component.less (0 bytes)
CREATE src/app/parent-com/parent-com.component.html (29 bytes)
CREATE src/app/parent-com/parent-com.component.spec.ts (650 bytes)
CREATE src/app/parent-com/parent-com.component.ts (285 bytes)
UPDATE src/app/app.module.ts (991 bytes)
wujiayudeMacBook-Pro:demo-test wjy$ ng g c childCom
CREATE src/app/child-com/child-com.component.less (0 bytes)
CREATE src/app/child-com/child-com.component.html (28 bytes)
CREATE src/app/child-com/child-com.component.spec.ts (643 bytes)
CREATE src/app/child-com/child-com.component.ts (281 bytes)
UPDATE src/app/app.module.ts (1083 bytes)
2.在父组件中使用子组件。
在父组件中增加一个按钮,每点击一次这个按钮,就会向子组件中传输一次数据。在父组件中使用子组件。(这里使用了 ng-zorro);
<nz-row>
<nz-form-label nz-col nzOffset="3" [nzSpan]="3">
父组件
</nz-form-label>
<nz-form-control nz-col nzSpan="5" >
<button nz-button (click)="parentEvent()">父组件的按钮</button>
</nz-form-control>
</nz-row>
<hr/>
<nz-row>
<nz-col nzOffset="3">
<app-child-com></app-child-com>
</nz-col>
</nz-row>
保存,浏览器中的页面如下:
为了区别,特意在子组件和父组件中加了一条线。现在要做的就是怎样向子组件传输数据?
通过输入型绑定把数据从父组件传到子组件
在子组件中使用输入型属性,即使用;@Input()装饰器;
编辑子组件的类文件,增加两个输入型属性的变量,在Angular中,对于输入型属性,是可以设置别名的。如下图:
但是根据Angular的风格指南,不推荐对输入型属性使用别名。
在子组件的显示这两个输入型属性;
<nz-row>
<nz-form-label nz-col nzOffset="3" [nzSpan]="3">
fromParent
</nz-form-label>
<nz-form-control nz-col nzSpan="5">
{{fromParent}}
</nz-form-control>
</nz-row>
<nz-row>
<nz-form-label nz-col nzOffset="3" [nzSpan]="3">
fromParentAlias
</nz-form-label>
<nz-form-control nz-col nzSpan="5">
{{fromParentAlias}}
</nz-form-control>
</nz-row>
在父组件的模版文件,对子组件的两个输入型属性进行变量的绑定:
在父组件的类文件中,新增两个变量,并添加button的点击事件。
保存刷新浏览器的页面:
然后点击按钮:页面就会发生如下变化:
这样,就将父组件中的toChild属性和子组件中的两个输入型属性变量进行了绑定。
获取输入型属性的变化
1.通过 setter 截听输入属性值的变化
修改子组件的类文件如下:
import {Component, Input, OnInit} from '@angular/core';
@Component({
selector: 'app-child-com',
templateUrl: './child-com.component.html',
styleUrls: ['./child-com.component.less']
})
export class ChildComComponent implements OnInit {
private fromParentData: string;
constructor() {
}
@Input()
set fromParent(info: string) {
// 处理得到的info
this.fromParentData = info;
}
get fromParent() {
return this.fromParentData;
}
ngOnInit() {
}
}
这个时候,就会由一个输入属性的 setter,来获取来自父组件的值,在这个中,可以对获取到的值进行处理,通过get fromParent() 去得到这个来自父组件并处理后的值,在子组件的模版文件上展示;当然,也可以不使用get fromParent() , 直接将属性 fromParentData 展示在模版文件中,但是要将 fromParentData 的private改为public 或者删掉。
<nz-row>
<nz-form-label nz-col nzOffset="3" [nzSpan]="3">
fromParent
</nz-form-label>
<nz-form-control nz-col nzSpan="5">
{{fromParent}}
</nz-form-control>
</nz-row>
保存,浏览器刷新的页面为:
2.通过ngOnChanges()来截听输入属性值的变化
这个时候,就需要用到生命周期钩子:OnChanges,修改子组件如下:
import {Component, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core';
@Component({
selector: 'app-child-com',
templateUrl: './child-com.component.html',
styleUrls: ['./child-com.component.less']
})
export class ChildComComponent implements OnInit, OnChanges {
private fromParentData: string;
@Input() fromParent: string;
constructor() {
}
// @Input()
// set fromParent(info: string) {
// // 处理得到的info
// this.fromParentData = info;
// }
//
// get fromParent() {
// return this.fromParentData;
// }
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges): void {
// 处理得到的fromParent
console.log(changes);
}
}
保存,刷新页面,就可以看到每次的变化
同过ngOnChanges() 可以清楚的得到输入型属性的每一次的变化和其前一次的变化的值,并且是不是第一次发生变化都可以知道。
父组件监听子组件的事件
有时候,子组件的操作完成了,或者事件处理完成了,需要向父组件传输数据。
在子组件中,创建一个button,每当子组件的这个button在用户点击的时候,就向父组件发送数据。这个时候就要用到@Output()装饰器,
修改子组件的模版文件如下:
<nz-row>
<nz-form-label nz-col nzOffset="3" [nzSpan]="3">
fromParent
</nz-form-label>
<nz-form-control nz-col nzSpan="5">
{{fromParent}}
</nz-form-control>
</nz-row>
<nz-row>
<nz-form-label nz-col nzOffset="3" [nzSpan]="3">
子组件
</nz-form-label>
<nz-form-control nz-col nzSpan="5">
<button nz-button (click)="toParentEvent()">子组件的按钮</button>
</nz-form-control>
</nz-row>
修改子组件的类文件如下:
import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges} from '@angular/core';
@Component({
selector: 'app-child-com',
templateUrl: './child-com.component.html',
styleUrls: ['./child-com.component.less']
})
export class ChildComComponent implements OnInit, OnChanges {
private fromParentData: string;
@Input() fromParent: string;
@Output() toParent = new EventEmitter<string>();
constructor() {
}
// @Input()
// set fromParent(info: string) {
// // 处理得到的info
// this.fromParentData = info;
// }
//
// get fromParent() {
// return this.fromParentData;
// }
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges): void {
console.log(changes);
}
toParentEvent() {
this.toParent.emit('点击了子组件');
}
}
子组件的 EventEmitter属性是一个输出属性,通常需要@Output装饰器。
修改父组件的模版文件如下:
<nz-row>
<nz-form-label nz-col nzOffset="3" [nzSpan]="3">
父组件
</nz-form-label>
<nz-form-control nz-col nzSpan="5">
<button nz-button (click)="parentEvent()">父组件的按钮</button>
</nz-form-control>
</nz-row>
<hr/>
<nz-row>
<nz-col nzOffset="3">
<app-child-com [fromParent]="toChild" (toParent)="getChild($event)"></app-child-com>
</nz-col>
</nz-row>
修改父组件的类文件如下:
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-parent-com',
templateUrl: './parent-com.component.html',
styleUrls: ['./parent-com.component.less']
})
export class ParentComComponent implements OnInit {
toChild: string;
count = 0;
constructor() {
}
ngOnInit() {
}
parentEvent() {
this.toChild = 'click' + this.count++;
}
getChild(info) {
console.log(info);
}
}
保存,刷新页面显示如下:
父组件与子组件通过本地变量互动
有时候,在父组件中,父组件不能使用数据绑定来读取子组件的属性或调用子组件的方法。但可以在父组件模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法。
修改父组件模版文件如下:
<nz-row>
<nz-form-label nz-col nzOffset="3" [nzSpan]="3">
父组件
</nz-form-label>
<nz-form-control nz-col nzSpan="5">
<button nz-button (click)="isChild.toParentEvent()">父组件的按钮</button>
</nz-form-control>
</nz-row>
<hr/>
<nz-row>
<nz-col nzOffset="3">
<app-child-com #isChild></app-child-com>
</nz-col>
</nz-row>
修改子组件的类文件如下:
import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges} from '@angular/core';
@Component({
selector: 'app-child-com',
templateUrl: './child-com.component.html',
styleUrls: ['./child-com.component.less']
})
export class ChildComComponent implements OnInit, OnChanges {
private fromParentData: string;
// @Input() fromParent: string;
// @Output() toParent = new EventEmitter<string>();
constructor() {
}
// @Input()
// set fromParent(info: string) {
// // 处理得到的info
// this.fromParentData = info;
// }
//
// get fromParent() {
// return this.fromParentData;
// }
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges): void {
console.log(changes);
}
toParentEvent() {
console.log('点击了子组件');
}
}
保存,每次点击按钮,控制台会输出:
这个时候,子组件就在父组件中作为一个本地变量,父组件中的button 就可以直接调用子组件中的方法了,这个时候,这个父组件本地变量就可以使用子组件中变量和方法了。虽然子组件作为本地变量存在于父组件中,但是这个时候是有一定局限性的,因为这个本地变量的所有属性和方法只能在父组件的模板中进行,不能在父组件的类文件中使用。
注意: 经测试发现,使用下列方法也可以调用到子组件的方法:
父组件模版文件:
<nz-row>
<nz-form-label nz-col nzOffset="3" [nzSpan]="3">
父组件
</nz-form-label>
<nz-form-control nz-col nzSpan="5">
<button nz-button (click)="parentEvent(isChild)">父组件的按钮</button>
</nz-form-control>
</nz-row>
<hr/>
<nz-row>
<nz-col nzOffset="3">
<app-child-com #isChild></app-child-com>
</nz-col>
</nz-row>
父组件类文件:
import {Component, OnInit, TemplateRef} from '@angular/core';
@Component({
selector: 'app-parent-com',
templateUrl: './parent-com.component.html',
styleUrls: ['./parent-com.component.less']
})
export class ParentComComponent implements OnInit {
toChild: string;
count = 0;
constructor() {
}
ngOnInit() {
}
parentEvent(child: any) {
child.toParentEvent();
}
// getChild(info) {
// console.log(info);
// }
}
保存以后,页面
在这里,是将这个本地变量,通过点击事件传参数的方式传进来了,然后在类文件中调用,同样能达到调用子组件的效果,但是不推荐使用。
父组件调用@ViewChild()于子组件交互
如果父组件的类文件需要读取子组件的属性值或调用子组件的方法,就不能使用本地变量方法。当父组件类需要这种访问时,可以把子组件作为 ViewChild,注入到父组件里面。然后就可以读取子组件的属性和子组件的方法了。
修改父组件模版文件为:
<nz-row>
<nz-form-label nz-col nzOffset="3" [nzSpan]="3">
父组件
</nz-form-label>
<nz-form-control nz-col nzSpan="5">
<button nz-button (click)="parentEvent()">父组件的按钮</button>
</nz-form-control>
</nz-row>
<hr/>
<nz-row>
<nz-col nzOffset="3">
<app-child-com></app-child-com>
</nz-col>
</nz-row>
修改父组件模版文件为:
import {Component, OnInit, TemplateRef, ViewChild} from '@angular/core';
import {Template} from '@angular/compiler/src/render3/r3_ast';
import {ChildComComponent} from '../child-com/child-com.component';
@Component({
selector: 'app-parent-com',
templateUrl: './parent-com.component.html',
styleUrls: ['./parent-com.component.less']
})
export class ParentComComponent implements OnInit {
// toChild: string;
// count = 0;
@ViewChild(ChildComComponent)
private child: ChildComComponent;
constructor() {
}
ngOnInit() {
}
parentEvent() {
this.child.toParentEvent();
}
// getChild(info) {
// console.log(info);
// }
}
这个时候,通过@ViewChild装饰器就可以访问到子组件的属性和方法了。
父组件和子组件通过服务来通讯
1.新建一个服务
ng g service parentChildService
wujiayudeMacBook-Pro:demo-test wjy$ ng g service parentChildService
CREATE src/app/parent-child-service.service.spec.ts (395 bytes)
CREATE src/app/parent-child-service.service.ts (147 bytes)
修改服务文件如下:
import {Injectable} from '@angular/core';
import {Subject} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ParentChildServiceService {
private transformData = new Subject<string>();
transform = this.transformData.asObservable();
constructor() {
}
setTransformData(data: string) {
this.transformData.next(data);
}
}
这里@Injectable({ providedIn: 'root'}) 装饰器是将这个服务注册到了整个应用都可以用了。在专注于两个组件时,将其注入于两个组件即可。这里的transformData用于接受每次传输过来的值,然后transformData作为一个Observable赋值给transform。
在子组件的类文件中:
import {Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core';
import {ParentChildServiceService} from '../parent-child-service.service';
import {Subscription} from 'rxjs';
@Component({
selector: 'app-child-com',
templateUrl: './child-com.component.html',
styleUrls: ['./child-com.component.less']
})
export class ChildComComponent implements OnInit, OnChanges, OnDestroy {
fromServiceData: Subscription;
constructor(private transformService: ParentChildServiceService) {
this.fromServiceData = this.transformService.transform.subscribe(data => {
console.log(data);
});
}
ngOnInit() {
}
ngOnDestroy(): void {
this.fromServiceData.unsubscribe();
}
ngOnChanges(changes: SimpleChanges): void {
console.log(changes);
}
}
在子组件中,fromServiceData作为一个Subscription对象将服务中的Observable进行订阅,从而,每当服务中的transform发生变化时,fromServiceData就会得到变化后的值。
页面如下:
注意:在这个页面中使用fromServiceData 变量,并在 ChildComComponent 被销毁时调用 unsubscribe()
退订。 这是一个用于防止内存泄漏的保护措施。实际上,在这个应用程序中并没有这个风险,因为 ChildComComponent 的生命期和应用程序的生命期一样长。但在更复杂的应用程序环境中就不一定了。