对于稍微接触过Angular组件的同学来说,组件间交互应该没有什么问题。
本文想追求的是用一个通俗解释,帮助自己理解的更准确。
零、知识铺垫
CSS选择器
在介绍父子组件之前,先要了解一个概念——selector、选择器
我们定义一个新组件时,一定会有这个属性:
@Component({
selector: 'app-village-edit', ①
templateUrl: './village-edit.component.html',
styleUrls: ['./village-edit.component.scss']
})
其中①就是选择器,就是告诉别的组件,如果想调用我这个组件,就要使用本组件的选择器<selectorName></selectorName>
来调用。
本质上就是定义了组件的HTML标签,就像常见的<p>
标签、<button>
标签一样。
一、什么是父子组件
就像现实中父母和孩子的关系是相对的一样,一个人对于它的父母来说,就承担了孩子的角色;对于它的孩子来说则承担了父母的角色。
父组件和子组件也是相对的。
假设,一个组件在自己的HTML模板中,通过选择器(也就是特定的HTML标签)来调用其他组件时。我们称这个组件为父组件,而那个被调用的组件称为子组件。
二、父组件调用子组件的方法
定义了两个类:
child.component.ts,它的选择器selector是: 'app-child'
parent.component.ts,它的选择器selector是: 'app-parent',
此时,在parent组件的HTMl中引用child组件的选择器:
<app-child></app-child>
这样就完成了子组件的调用。
此时,如果通过路由加载父组件,就会发现子组件也会在特定的位置被渲染出来。
三、父组件向子组件传值
子组件使用@input装饰器接收数据
子组件从父组件接收的值,会保存到子组件的变量中。
所以用来接收传值的变量与普通变量唯一的区别,就是在常规的变量上增加一个@input()注解。
定义普通变量是这样的:
master = 'Master';
如果用来接收传值,只要改成这样:
@Input() master = 'Master';
这样,master变量默认是'Master'字符串。
但如果父组件向其传值,变量就变成了接收的值。
父组件使用方括号[]发送数据
常规方式调用子组件:
<app-child></app-child>
如果子组件可以接收数据,就可以用[propertyName] = value的方法来传值。
例如:
<app-child [master]="hero"> </app-child>
用这种写法可以实现:一旦组件渲染完成后,子组件中的master变量就是'hero'的值了。
当父组件中的变量值变化时,子组件也会同步变化,
也就是说,子组件可以监听传过来的值的变化信息。
升级:子组件通过set方法监听传入数据变化
在上面的方式中,对于传过来的值,虽然可以监听变化,但局限在于:子组件只能直接使用传入的值。
如果想对传入的值进行处理或过滤,就要稍微调整一下子组件。
常规情况下,子组件是通过给变量加上@Input装饰器来接收参数的:
@Input() name = 'name';
如果想处理参数,只需要把接收传值的变量变成set方法即可:
@Input()
get name(): string { return this._name; }
set name(name: string) {
// 此处可以增加其他处理逻辑
this._name = name;
}
private _name = '';
此时,_name
是内部变量,当父组件对于name属性传入值的时候,会自动执行set name方法给_name
赋值并增加其他的处理逻辑。
另一种升级:子组件通过ngOnChanges()生命周期钩子监听传入数据变化
官方文档中写到:“当需要监视多个、交互式输入属性的时候,ngOnChanges()比用属性 setter 方法更合适。”
常规情况下,子组件是通过给变量加上@Input装饰器来接收参数的:
@Input() param1 = 'string1';
@Input() param2 = 'string2';
当我们要监听多个变量的变化并做出反应时,可以用ngOnChanges()方法:
@Input() param1 = 'string1';
@Input() param2 = 'string2';
ngOnChanges(changes: SimpleChanges) { ①
for (const propName in changes) { ②
// 通过变量名获取变化信息
const changedProp = changes[propName];
// 获取上一个值
const from = JSON.stringify(changedProp.previousValue); ③
// 获取当前值
const to = JSON.stringify(changedProp.currentValue); ④
// 此处可以添加其他处理过程了 ⑤
}
}
① 执行ngOnChanges()方法时,可以用一个SimpleChanges参数来获得当前组件所有参数的变化情况。
② 通过循环获得每一个参数的上一个值和当前值。
③ 获得上一个值
④ 获得当前值
⑤ 根据业务逻辑添加其他处理过程
注:由于ngOnChanges方法调用非常频繁,会导致性能问题或者软件崩溃,所以建议少用。
四、子组件向父组件传值
子组件向父组件弹射事件
刚刚讲到了子组件如何获取父组件的传入的变量,如何监听父组件的变化,以及如何处理传入的值。
接下来讲反向的传输:父组件如何监听子组件的变化,并做出反应。
定义普通变量是这样的:
param1 = 'String1';
如果想把这个变量暴露给父组件,需要在变量前加入@output()装饰器,并且给他赋值一个变量弹射器:
@Output() param1 = new EventEmitter<string>();
此处EventEmitter是变量弹射器,EventEmitter需要一个确定的类型。
但此时,这个param1变量就不能再用等号"="赋值了,如果想让父组件监听到变化,就需要用弹射方法.emit
:
this.param1.emit("String2");
接下来前往父组件。
父组件监听子组件弹射的事件
刚刚已经在子组件设置好了暴露的变量,那么父组件如何接收呢?
常规的父组件调用子组件:
<app-child></app-child>
如果想监听子组件的某个变量,可以使用圆括号():
<app-child (param1)="function1($event)">
</app-child>
$event 是Angular内置的事件变量。
function1我们在父组件中定义的处理变化的方法。
使用方式如下:
function1(param2: boolean) {
// 这个param2为我们自己定义的参数名,
// 本质上是子组件中变化的param1参数,但不用和子组件中的参数名相同
// 在此处增加处理过程即可
}
此时,当param1的值发生变化,就会执行function1,并且传入一个事件,事件的实质内容就是子组件定义的param1参数。
function1方法把参数作为param2接收,并添加处理过程。
五、总结
- Angular中,在HTML通过selector选择器调用的组件称为子组件。
- 父组件向子组件传值使用方括号[]
- 子组件有两种方式接收值: @input + 变量名、@Input + set方法
- 子组件想父组件传递事件使用EventEmitter
- 父组件接收事件使用圆括号(),并声明一个处理方法用来调用
熟悉的风格,一图胜千言:
六、后记
是不是有似曾相识的感觉,在刚开始接触Angular时就知道,可以使用方括号[]来绑定原生HTML标签的某些属性,例如:
<p [id]="sayHelloId" [style.color]="fontColor">
You can set my color in the component!
</p>
另一方面,还有一个相似之处就是,Angular中也是使用圆括号()来绑定原生HTML标签的某个方法,例如:
<button (click)="onClick()">
点我!
</button>
这些是巧合吗?并不是。
我们可以这样理解:
Angular中所有的原生HTML标签都变成了组件。
之所以很多标签中可以用方括号[]绑定属性、使用圆括号()绑定方法,是因为Angular已经为我们扩展了原生的HTML标签,使它们具备了接收和发送数据的能力!
换言之,在Angular内部的组件中,已经为我们加上了许许多多的@input和@output装饰器,我们才能方便的绑定这些属性和方法。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。