在组件化开发中,会将相同的功能或者业务封装为独立的组件,以达到组件复用,在各个组件的组合使用中,避免不了在独立组件间进行数据和事件的传递。
在Angular1中,我们常常会使用$scope
来进行交互绑定,这里我们就按照官方文档的顺序简单聊下Angular2中的交互,主要的交互方式大致有一下途径:
接下来将通过一个示例来完成各种方法的学习。
** 准备 **
首先让我们准备两个组件OuterPanel
和InnerPanel
,两者为父子级嵌套关系。
创建InnerPanel
组件,代码如下:
innerComponent.ts1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { Component } from '@angular/core';
@Component({ selector: 'innerPanel', template: ` <div class="inner-panel">inner Panel</div> `, styles: [` .inner-panel{ border: 1px #000 solid; padding: 20px; } `] }) export class InnerComponent {}
|
创建OuterPanel
组件,代码如下:
outerComponent.ts1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { Component } from '@angular/core';
@Component({ selector: 'outerPanel', template: ` <div class="outer-panel"> <span>outer Panel</span> <innerPanel></innerPanel> </div> `, styles: [` .outer-panel{ border: 1px red solid; width: 500px; padding: 20px; } `] }) export class OuterComponent {}
|
然后我们在模板中引用组件
app.component.html1
| <outerPanel></outerPanel>
|
运行示例,将会在界面上渲染出如下结果:
接着我们将扩展上面的两个组件,来完成各种方式的数据交互。
** 通过数据绑定 **
数据绑定在前面的也有过介绍,这里简单的再提一下。我们需要做个扩展,要求是:在InnerPanel
组件中,声明一个变量innerName
,然后在InnerPanel
的模板中进行展示。
innerComponent.ts1 2 3
| export class InnerComponent { innerName = 'Daniel'; }
|
在组件中添加变量的声明代码,并修改组件模板进行输出展示。
innerComponent.ts1 2 3 4 5 6
| template: ` <div class="inner-panel"> <span>inner Panel</span> <div>innerName: {{innerName}}</div> </div> `,
|
保存运行代码,将会看到变量在inner Panel区域输出了。
当前我们只是简单的输出组件自身的变量值,那么如果是在outerPanel
中进行赋值呢?默认组件中的属性都是私有的,需要设置公共后才能被外界访问,修改InnerPanel
组件代码如下:
innerComponent.ts1 2 3 4 5 6 7
| import { Component, Input } from '@angular/core'; ...
export class InnerComponent { @Input() innerName = 'Daniel'; }
|
然后在调用组件的时候,进行赋值:
outerComponent.ts1 2 3 4 5 6 7
| template: ` <div class="outer-panel"> <span>outer Panel</span> <!-- 常量赋值 --> <innerPanel innerName="outerName"></innerPanel> </div> `,
|
可以看到innerName="outerName"
已经生效,传值到了内部组件,这里,如果InnerPanel
组件不添加@Input
注解,运行时则会报错:
异常信息1 2
| Uncaught Error: Template parse errors: Can't bind to 'innerName' since it isn't a known property of 'innerPanel'. (" <span>outer Panel</span>
|
所以,在开发的时候一定要注意属性是否需要设置为公共的。
前面我们传的值是个常量值,实际开发中多数都是动态的数据,那么下面我们来学习下动态值的传递,其实非常简单,在开始之前,先理解下这几条语句的区别
示例1 2 3
| <innerPanel innerName="outerName"></innerPanel> <innerPanel innerName="{{outerName}}"></innerPanel> <innerPanel [innerName]="outerName"></innerPanel>
|
语句1:这个已经见到过了,就是将innerPanel
的innerName
属性赋值为outerName
。
语句2、3:这两条语句的效果是一样的,都可以实现动态数据的绑定。语句2通过插值表达式来绑定输出属性值,然后会绑定更新到innerPanel
组件中。语句3直接使用属性绑定来绑定组件属性。
接着我们继续扩展下代码,添加一个按钮事件,来实现属性值的变化,来测试一下动态数据的绑定。
outerComponent.ts1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ...
template: ` <div class="outer-panel"> <span>outer Panel</span> <button (click)="change()">Change</button> <!-- 这里同时使用两种方式 --> <innerPanel [innerName]="outerName2Inner"></innerPanel> <innerPanel innerName="{{outerName2Inner}}"></innerPanel> </div> `, ...
export class OuterComponent { outerName2Inner = "OuterDaniel"; i = 0; change(){ this.outerName2Inner = `Outer Changed:${this.i++}`; } }
|
通过点击按钮,我们可以发现外部组件中值的变化会通过绑定关系,传递到内部组件中进行展示。
接下来我们来试下,函数的绑定,实现将
** 通过组件引用 **
组件引用的通讯方式,主要有可以细分为两种 本地变量(local variable)、模板引用变量(Template reference variables) 和 @ViewChld(),这两种方式的核心思想都是在当前的上下文中创建一个对子组件的引用,然后通过这个创建的引用来访问子组件的属性和方法。
最要的区别是:本地变量仅能在组件的模板中使用,也就是说只能在HTML中使用。接下来应该是什么,大家应该已经想到了,对的,**@ViewChild()**仅能在组件的JavaScript逻辑中使用的了。接下来,我们继续扩展前面的例子,然后进行学习测试。
先对模板引用变量进行下介绍,分别在组件中加入相应的测试代码
innerComponent.ts1 2 3 4 5 6 7 8 9 10 11 12
| import { Component, Input } from '@angular/core'; ... export class InnerComponent { ... innerVarName = 'innerVarName'; innerVarNameChange(){ this.innerVarName = "innerChanged"; } ... }
|
outerComponent.ts1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ...
template: ` <div class="outer-panel"> <span>outer Panel</span> <button (click)="change()">Change</button> <!-- 添加两个按钮,分别通过子组件引用来进行测试:输出子组件的属性和函数调用 --> <button (click)="innerPanel1.innerVarChange()">{{innerPanel1.innerVarName}}</button> <button (click)="innerPanel2.innerVarChange()">{{innerPanel2.innerVarName}}</button> <!-- 使用#符号,对子组件设置引用 --> <innerPanel #innerPanel1 [innerName]="outerName2Inner"></innerPanel> <!-- 使用ref-前缀,对子组件设置引用 --> <innerPanel ref-innerPanel2 innerName="{{outerName2Inner}}"></innerPanel> </div> `, ...
|
点击新增的两个按钮,我们可以看到,对应的Button值已经发生了变化。
接着我们来介绍下**@ViewChild()**的方式,主要在OuterComponent
中修改下引用方式,代码如下:
outerComponent.ts1 2 3 4 5
| import { AfterViewInit, Component, ViewChild, ViewChildren, QueryList } from '@angular/core';
import { InnerComponent } from './innerComponent'; ...
|
其中ViewChild
和ViewChildren
都是可以实现子组件的注入,主要区别在于ViewChildren
的注入结果是一个QueryList
的集合。
ViewChild
和ViewChildren
都可以使用通过名称
和类型
来进行注入,我们在outerComponent.ts
中加入如下代码:
outerComponent.ts1 2 3 4 5 6 7 8 9 10 11 12
| ...
@ViewChild(InnerComponent) innerPanel0: InnerComponent;
@ViewChild('innerPanel1') innerPanel1: InnerComponent; @ViewChild('innerPanel2') innerPanel2: InnerComponent; ...
@ViewChildren(InnerComponent) innerPanels: QueryList<InnerComponent>;
@ViewChildren('innerPanel1') innerPanel3: QueryList<InnerComponent>;
|
接下来我们要在父组件中来使用下子组件的引用,我们前面的代码导入了AfterViewInit
,然后让组件OuterComponent
实现接口。
outerComponent.ts1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| ...
<innerPanel #innerPanel1 [innerName]="outerName2Inner"></innerPanel> <innerPanel ref-innerPanel2 innerName="{{outerName2Inner + '-diff'}}"></innerPanel> ... export class OuterComponent implements AfterViewInit { ... ngAfterViewInit() { console.log("@ViewChild(InnerComponent):", this.innerPanel0); console.log("@ViewChild('innerPanel1'):", this.innerPanel1); console.log("@ViewChild('innerPanel2'):", this.innerPanel2); console.log("@ViewChildren(InnerComponent):", this.innerPanels); console.log("@ViewChildren('innerPanel1'):", this.innerPanel3); } }
|
我们看到输出结果:
1:蓝框中所示,通过类型引用,默认会使用第一个自组建势力,如果换一下模板中两个子组件的顺序,则这里也会改变
2:红框中所示,QueryList是个Array,只有一个长度,表示只拿到了指定的子组件
既然已经拿到了组件的引用,那么就可以访问组件的属性和函数了。
** 通过服务组件 **
父子组件通过服务组件来进行通讯,主要利用的就是服务组件的单例模式,然后通过共享服务实例的数据来完成交互。这里我们将会在学习依赖注入
和事件
时进行介绍,后续会更新链接至此。
** 其他 **
当然,父子组件间的通讯还有一些其他的方法扩展,比如设置属性的setter
访问器或使用组件的ngOnChanges()
来监听输入值改变,两种方式的使用原型如下,大家可以自行测试使用一下,具体的方法选用,还得主要看具体的业务场景,来选择合适的方法。
setter1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| export class NameChildComponent { private _name = ''; @Input() set name(name: string) { this._name = (name && name.trim()) || '<no name set>'; } get name(): string { return this._name; } }
import { Component, Input, OnChanges, SimpleChange } from '@angular/core'; export class VersionChildComponent implements OnChanges { @Input() major: number; @Input() minor: number; changeLog: string[] = []; ngOnChanges(changes: {[propKey: string]: SimpleChange}) { let log: string[] = []; for (let propName in changes) { let changedProp = changes[propName]; let to = JSON.stringify(changedProp.currentValue); if (changedProp.isFirstChange()) { log.push(`Initial value of ${propName} set to ${to}`); } else { let from = JSON.stringify(changedProp.previousValue); log.push(`${propName} changed from ${from} to ${to}`); } } this.changeLog.push(log.join(', ')); } }
|