官网:https://angular.cn/guide/component-interaction
1、父组件 向 子组件 传递数据:Input
父组件中改变某变量X(任何数据类型), 子组件用 @Input 接收该变量
父组件:
<!-- parentComponent -->
<app-child [name]="'childName'"></app-child>
子组件:
@Input() public name:string = '';
如果传入子组件的数据,仅仅在子组件中用来显示,是不需考虑输入变化的(双向绑定就帮我们自动实现了),而往往还要在input变化时做一些逻辑处理,此时就需要对输入属性的变化进行监听,通常有两种方式:
方法1: 通过 setter 截听输入属性值的变化
针对一个输入属性,设置setter,以拦截父组件中值的变化,并采取行动。
更多知识:https://my.oschina.net/sunlightday/blog/3118148
子组件:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-name-child',
template: '<h3>"{{name}}"</h3>'
})
export class NameChildComponent {
@Input()
get name(): string { return this._name; }
set name(name: string) {
// 当父组件输入的name变化的时候,set方法自动截听,然后在这里做相应的处理
// 入参name就是最新的值,而当前旧值就是本地私有变量_name this._name = (name && name.trim()) || '<no name set>';
}
private _name = '';
}</pre>
父组件:
import { Component } from '@angular/core';
@Component({
selector: 'app-name-parent',
template: `
<h2>Master controls {{names.length}} names</h2>
<app-name-child *ngFor="let name of names" [name]="name"></app-name-child>
`
})
export class NameParentComponent {
// Displays 'Dr IQ', '<no name set>', 'Bombasto'
names = ['Dr IQ', ' ', ' Bombasto '];
}
方法2: 通过ngOnChanges()来截听输入属性值的变化
使用
OnChanges
生命周期钩子接口的ngOnChanges()
方法来监测所有输入属性值的变化并做出回应。当需要监视多个、交互式输入属性的时候,本方法比用属性的 setter 更合适。
生命周期钩子:https://angular.cn/guide/lifecycle-hooks
**setter vs ngOnChanges
如果要对某个输入变量进行变化监听,setter好用
如果多个输入变量都需要进行变化监听,并且在监听后的逻辑处理涉及到多个输入属性,ngOnChanges好用 -- 可以在这个方法中统一处理
2、子组件 向 父组件 传递数据: Output EventEmitter
首先子组件暴露一个 EventEmitter 属性,当子组件中改变某变量X(任何数据类型),或者想要传递消息,子组件就利用EventEmitter属性的emit方法,将事件发射出去, 父组绑定该事件,并定义相应的方法做出响应
子组件:
Output() childEmit: EventEmitter<T> = new EventEmitter<T>();
// 对某变量data一顿操作后,发射出去
this.childEmit.emit(data);</pre>
父组件:
在子组件引用上面绑定:(eventEmitter)="模板表达式",就像响应(click)事件一样。
<app-child (childEmit)="getData($event)"></app-child></pre>
3、父组件 访问 子组件: 本地变量
在父组件模板里,新建一个本地变量来代表子组件(其实就是子组件的引用),然后利用这个变量来读取子组件的属性和调用子组件的方法, 比较方便实用
父组件:
<app-base-grid #grid></app-base-grid>
<span>{{ grid.name }}</span> // 直接获取子组件变量
<button (click)="grid.func()">直接调用子组件方法</button></pre>
这个本地变量方法简单便利,但是它也有局限性:父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的代码对子组件没有访问权。即,仅限于在html代码中操作
4、父组件 访问 子组件: ViewChild 方法
如果父组件的类 需要读取子组件的属性值或调用子组件的方法,就不能使用本地变量方法。当父组件类 需要这种访问时,可以把子组件作为 ViewChild,注入到父组件里面。
父组件:
<app-base-grid #grid></app-base-grid>
<span>{{ grid.name }}</span> // 直接获取子组件变量
<button (click)="grid.func()">直接调用子组件方法</button></pre>
@ViewChild('grid') grid: BaseGridComponent; // 表格
// 我还可以干些别的
this.grid 巴拉巴拉</pre>
5、非父子组件通信: service 实例共享
组件之间共享同一个服务实例,利用该服务在组件之间实现双向通讯。其实就是服务实例的一个变量,在引用服务的各个组件中都可以被改变和读取
服务:
import { Component, Injectable, EventEmitter } from '@angular/core';
@Injectable()
export class myService {
public info: string = '';
}
组件 1 向 service 传递信息
import { Service1 } from '../../service/service1.service';
public constructor(
public service: Service1, // 引用服务
) { }
public changeInfo():void {
this.service.info = this.service.info + '1234'; // 写数据
}
组件 2 从 service 获取信息
import { Service2 } from '../../service/service2.service';
public constructor(
public service: Service2, // 引用服务
) { }
public showInfo() {
console.log(this.service.info); // 读取数据
}
6、非父子组件通信: Subject(发布订阅)
发布订阅模式,当发布者数据改变时,订阅者也能得到及时响应
1、定义事件
@Injectable()
export class AppService {
// 开立诊断,诊断更新后通知,需要的地方注册监听
afterDiagnosticUpdate: EventEmitter<'ADD' | 'DELETE' | 'INVALID'>;
constructor() {
this.afterDiagnosticUpdate = new EventEmitter();
}
}
1、在发布事件的组件中进行 emit() :发出包含给定值的事件。
this.appService.afterDiagnosticUpdate.emit('ADD');
2、在需要知道该事件的组件中进行订阅 subscribe():即注册此实例发出的事件的处理器。
// 注册监听器(消息订阅者)
this.msgReader = this.appService.afterDiagnosticUpdate.subscribe((opt: string) => {
// 监听后的处理
doSomething......
});
// 组件销货时,注销监听器
ngOnDestroy(): void {
this.msgReader.unsubscribe();
}
重要:虽然监听器是在组件中定义并创建,但是组件销毁时,监听器并未自动销毁,需要调用unsubscribe 来执行销货。 这个非常重要,否则轻则内存泄露,重则导致逻辑出现异常(逻辑处理在一个已经死掉的组件中执行,并且执行结果有效)。
7、路由传参通信
1、 查询参数中传递数据
在a标签上添加一个参数queryParams,接收要传入的参数对象
<a [routerLink]="['/tab4']" [queryParams]="{id:3}" >tab4</a></pre>
在跳转后进入的页面(组件),注入ActivatedRoute, 用通过对queryParams订阅的方式,来接收传递来的参数:
constructor( private activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.activatedRoute.queryParams.subscribe(params => {
// 接收参数
this.id = params.id;
});
}
2、 路由路径(url)中传递参数
修改路由配置文件path还是以tab4组件为例:
{
path: 'tab4/:name',
component:Tab4Component,
children: []
},
我们在后面添加/:name,name即为传递的参数名
a标签设置如下,routerLink后面数组的第二个参数为传递的参数值
<a [routerLink]="['/tab4','我是url传递参数']" [queryParams]="{id:3}" >tab4</a></pre>
在跳转后进入的页面(组件),注入ActivatedRoute, 用通过对params订阅的方式,来接收传递来的参数(注意和1对比):
constructor( private activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.activatedRoute.params.subscribe(params => {
// 接收参数
this.name = params.name;
});
}
也可以这样写(**snapshot透过快照的方式获取值 **)
( private activatedRoute: ActivatedRoute) {}
ngOnInit() {
// 接收参数
this.name = this.activatedRoute.snapshot.params['name']
}
snapshot快照和subscribe订阅差别在于:订阅实时监视着数据的变化,而快照只在调用时改变一次,如果在确定路由参数只在组件初次创建时获取一次可以采用快照,如需组件内路由参数可能实时变化,则采取订阅
3、 路由配置中设置静态数据
修改路由配置文件path还是以tab4组件为例:
{
path: 'tab4/:name',
component:Tab4Component,
children: [],
data:[{Data:'路由配置静态数据'}]
}
tab4组件中获取并赋值数据
private data
ngOnInit() {
// this.id=this.activatedRoute.snapshot.queryParams["id"]
// this.name=this.activatedRoute.snapshot.params['name']
this.activatedRoute.queryParams.subscribe((params:Params)=>{
this.id=params['id']
})
this.activatedRoute.params.subscribe((params:Params)=>{
this.name=params['name']
})
//下面为新加入的
this.data=this.activatedRoute.snapshot.data[0]["Data"]
}