Angular开发实践(四):组件之间的交互
在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 截听输入属性值的变化
在实际应用中,我们往往需要在某个输入属性值发生变化的时候做相应的操作,那么此时我们需要用到输入属性的 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 里面你可以做更多的其它操作,程序的灵活性就更强了。
通过ngOnChanges()来截听输入属性值的变化
通过 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本地变量,然后在模板中获取子组件的属性:
<p>
<!-- 获取子组件的属性readyInfo,显示:子组件DemoChildComponent初始化完成! -->
readyInfo: {{demoChild.readyInfo}}
</p>
父组件调用@ViewChild()
本地变量方法是个简单便利的方法。但是它也有局限性,因为父组件-子组件的连接必须全部在父组件的模板中进行。父组件本身的代码对子组件没有访问权。
如果父组件的类需要读取子组件的属性值或调用子组件的方法,就不能使用本地变量方法。
当父组件类需要这种访问时,可以把子组件作为 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学习笔记之组件之间的交互
1.@Input:可设置属性 当它通过属性绑定的形式被绑定时,值会“流入”这个属性. 在子组件中使用,例如:@Input()name:string 父组件定义宾亮,并在父组件的模板中绑定,例如: 子组 ...
- 第四节:Vuejs组件及组件之间的交互
一. 组件及其交互 1.组件的注册 (1).全局注册 Vue.component('组件名称', { }) 第1个参数是标签名称,第2个参数是一个选项对象. 选项参数包括 data:必须是一个func ...
- Angular开发实践(一):环境准备及框架搭建
引言 在工作中引入Angular框架将近一年了,在这一年中不断的踩坑和填坑,当然也学习和积累了很多的知识,包括MVVM框架.前后端分离.前端工程化.SPA优化等等.因此想通过Angular开发实践这系 ...
- Angular开发实践(七): 跨平台操作DOM及渲染器Renderer2
在<Angular开发实践(六):服务端渲染>这篇文章的最后,我们也提到了在服务端渲染中需要牢记的几件事件,其中就包括不要使用window. document. navigator等浏览器 ...
- Vue中独立组件之间数据交互
独立组件之间数据交互:通过自定义事件 组件A中的[数据],传递给组件B 1.创建组件A,组件B 2.组件B在实例创建完成时就开始监听事件[等待接收数据]:钩子 3.组件A中触发事件,发送数据 注意:接 ...
- Angular开发实践(五):深入解析变化监测
什么是变化监测 在使用 Angular 进行开发中,我们常用到 Angular 中的绑定--模型到视图的输入绑定.视图到模型的输出绑定以及视图与模型的双向绑定.而这些绑定的值之所以能在视图与模型之间保 ...
- Angular开发实践(三):剖析Angular Component
Web Component 在介绍Angular Component之前,我们先简单了解下W3C Web Components 定义 W3C为统一组件化标准方式,提出Web Component的标准. ...
- 8.【nuxt起步】-vue组件之间数据交互
那么现在问题来了,我现在是在index.vue获取了服务端的数据,怎么传值到maincontent.vue?当然你也可以把获取数据放在maincontent.vue,但假如有些数据同时在header, ...
- Vue组件之间数据交互与通信
Vue 的组件作用域都是孤立的,不允许在子组件的模板内直接引用父组件的数据.必须使用特定的方法才能实现组件之间的数据传递. 一.父组件向子组件传递数据 在 Vue 中,可以使用 props 向子组件传 ...
随机推荐
- 前端时间戳timestamp相关总结:
一.JavaScript获取当前时间戳的方法 第一种方法:var timestamp = Date.parse(new Date());结果:1280977330000 第二种方法:var times ...
- 跨站请求伪造攻击 CSRF
摘录: 1.跨站点请求伪造 首先,什么是跨站点请求伪造? 跨站点请求伪造-CSRF(Cross Site Request Forgery):是一种网络攻击方式. 说的白话一点就是,别的站点伪造你的请求 ...
- 阿里舆情︱舆情热词分析架构简述(Demo学习)
本节来源于阿里云栖社区,同时正在开发一个舆情平台,其中他们发布了一篇他们所做的分析流程,感觉可以作为案例来学习.文章来源:觉民cloud/云栖社区 平台试用链接:https://prophet.dat ...
- 实战DeviceIoControl 之五:列举已安装的存储设备
Q 前几次我们讨论的都是设备名比较清楚的情况,有了设备名(路径),就可以直接调用CreateFile打开设备,进行它所支持的I/O操作了.如果事先并不能确切知道设备名,如何去访问设备呢? A 访问设备 ...
- CentOS配置日志集中管理
①首先有产生日志的服务器和储存日志的服务器 ②产生.接收日志的服务器都必须安装rsyslog服务(可以通过yum.rpm.源码包安装),rsyslog支持C/S模式 ③日志存储服务器需要编辑rsysl ...
- 什么是PostBack(译)
什么是PostBack(译) What is a postback? 下面的内容是针对ASP.NET新手的 PostBack什么时候被引发? PostBack由客户端浏览器引发.通常是用户操作(点击按 ...
- mini-css-extract-plugin 的用法(webpack4)
今天在使用webpack的extract-text-webpack-plugin插件提取单独打包css文件时,报错,说是这个插件要依赖webpack3的版本. 后面查了一下,webpack4得使用mi ...
- 期望$DP$ 方法总结
期望\(DP\) 方法总结 这个题目太大了,变化也层出不穷,这里只是我的一点心得,不定期更新! 1. 递推式问题 对于无穷进行的操作期望步数问题,一般可用递推式解决. 对于一个问题\(ans[x]\) ...
- 【BZOJ1483】【HNOI2009】梦幻布丁(启发式合并,平衡树)
[BZOJ1483][HNOI2009]梦幻布丁 题面 题目描述 N个布丁摆成一行,进行M次操作.每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色.例如颜色分别为1,2,2,1 ...
- Python标准库概览
Python标准库通常被称为"自带的电池",自然地提供了广泛的功能,涵盖了大概200个左右的包与模块.不断有高质量的包或模块被开发出来,极大的丰富了标准库.但有些模块放在标准库中很 ...