Angular 组件之间的交互的示例代码


Posted in Javascript onMarch 24, 2018

在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属性值。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
重写javascript中window.confirm的行为
Oct 21 Javascript
jquery怎样实现ajax联动框(二)
Mar 08 Javascript
基于KMP算法JavaScript的实现方法分析
May 03 Javascript
jQuery之折叠面板的深入解析
Jun 19 Javascript
jQuery实现“扫码阅读”功能
Jan 21 Javascript
jQuery实现仿淘宝带有指示条的图片转动切换效果完整实例
Mar 04 Javascript
学习使用bootstrap基本控件(table、form、button)
Apr 12 Javascript
BootStrap Table 后台数据绑定、特殊列处理、排序功能
May 27 Javascript
详解Angular-cli生成组件修改css成less或sass的实例
Jul 27 Javascript
node.js中使用Export和Import的方法
Sep 18 Javascript
js将当前时间格式化为 年-月-日 时:分:秒的实现代码
Jan 20 Javascript
如何解决js函数防抖、节流出现的问题
Jun 17 Javascript
Vue中computed与methods的区别详解
Mar 24 #Javascript
javaScript实现鼠标在文字上悬浮时弹出悬浮层效果
Apr 12 #Javascript
使用Angular CLI进行单元测试和E2E测试的方法
Mar 24 #Javascript
使用Angular CLI进行Build(构建)和Serve详解
Mar 24 #Javascript
使用Angular CLI生成路由的方法
Mar 24 #Javascript
使用Angular CLI从蓝本生成代码详解
Mar 24 #Javascript
详解Vue文档中几个易忽视部分的剖析
Mar 24 #Javascript
You might like
PHP网上调查系统
2006/10/09 PHP
php面向对象全攻略 (一) 面向对象基础知识
2009/09/30 PHP
PHP daddslashes 使用方法介绍
2012/10/26 PHP
保存到桌面、设为桌面且带图标的PHP代码
2013/11/19 PHP
php下载文件源代码(强制任意文件格式下载)
2014/05/09 PHP
PHP使用redis实现统计缓存mysql压力的方法
2015/11/14 PHP
PHP数组函数知识汇总
2016/05/12 PHP
Centos7 Yum安装PHP7.2流程教程详解
2019/07/02 PHP
javascript之ESC(第二类混淆)
2007/05/06 Javascript
基于JQuery制作的产品广告效果
2010/12/08 Javascript
js 判断计算字符串长度/判断空的简单方法
2013/08/05 Javascript
javascript 获取iframe里页面中元素值的方法
2014/02/17 Javascript
JSON格式化输出
2014/11/10 Javascript
node.js中的buffer.Buffer.isEncoding方法使用说明
2014/12/14 Javascript
JavaScript获取网页中第一个链接ID的方法
2015/04/03 Javascript
JavaScript取得键盘按下方向键是哪个的方法
2015/08/04 Javascript
jquery实现简易的移动端验证表单
2015/11/08 Javascript
借助FileReader实现将文件编码为Base64后通过AJAX上传
2015/12/24 Javascript
JavaScript仿支付宝密码输入框
2015/12/29 Javascript
JS中如何比较两个Json对象是否相等实例代码
2016/07/13 Javascript
js中遍历Map对象的方法
2016/07/27 Javascript
Javascript中字符串相关常用的使用方法总结
2017/03/13 Javascript
各种选择框jQuery的选中方法(实例讲解)
2017/06/27 jQuery
详解Vue.js组件可复用性的混合(mixin)方式和自定义指令
2017/09/06 Javascript
vue-cli项目中使用公用的提示弹层tips或加载loading组件实例详解
2018/05/28 Javascript
[36:16]完美世界DOTA2联赛PWL S3 access vs Rebirth 第一场 12.19
2020/12/24 DOTA
Python多层装饰器用法实例分析
2018/02/09 Python
Python查找文件中包含中文的行方法
2018/12/19 Python
Pytorch训练过程出现nan的解决方式
2020/01/02 Python
Topshop法国官网:英国快速时尚品牌
2018/04/08 全球购物
读书月活动方案
2014/05/22 职场文书
舞蹈教育学专业自荐信
2014/06/15 职场文书
四风问题自我剖析材料
2014/10/07 职场文书
新教师个人工作总结
2015/02/06 职场文书
员工规章制度范本
2015/08/07 职场文书
MySQL完整性约束的定义与实例教程
2021/05/30 MySQL