详解Angualr 组件间通信


Posted in Javascript onJanuary 21, 2017

Angualr 组件间通信

约定: 遵循Angular官方的说法,下文中的AngularJS代指1.x版本,Angular代指Angular2及以后的升级版本。

采用Angular(或者任意MV*)的前端框架开发单页应用(SPA)时,我们都可能会遇见如下的场景:

A组件和B组件之前需要相互通信,或是A路由状态需要知道B路由状态的信息等等业务需求。

这个时候就需要设计到采用一套合理的通信方案来解决数据同步,数据通信的问题。

AngularJS 组件间的数据通信

在AngularJS中,也就是Angular JS 1.x版本中,我们需要实现控制器间的通信,有很多种方案,常见的有:

1. 采用 SharedService, 利用共享的公共服务来实现数据交换。  

AngularJS中的Service被设计成单例的,这为这一方案,提供来底层的实现可行性,这一方案也是被广泛采用的。

2. 利用AngularJS提供的事件机制,$rootScope.$broadcast/ $scope.$emit 配合 $on 方法实现。

该方案的实现具备一定的局限性,比如:$emit方法只能向上传递事件,而不能实现向下的事件传播。但是进行合理的搭配组合已经基本够用了。

3. 利用浏览器的SessionStorage或者LocalStorage进行数据交换。

由于浏览器提供的这两类本地存储方案都提供了相应的storage事件,所以我们也可以使用该方案进行数据交换。使用该方案是应该注意及时清理无关数据。

4. 采用响应式的编程思想或者观察者模式的应用。关于这一类实现,需要经历一个编程思想的转变,之后会通过专门的文章进行记录。

5. 自身实现共享变量池。这个难度比较大,没有一定的设计能力并不推荐。

由于AngularJS并不是本文的重点,所以这里只简单的提一下。后面介绍的Angular的方案也有许多可以通用的地方。

Angular 组件间的数据通信

SharedService

共享服务方案在新的Angular中依然可以使用,而且无需额外的学习成本。这里在之前的学习笔记里有记录,不再纪录了。

SharedService 搭配 RxJS

听说 SharedService 和 RxJS 搭配更实用哦!这里的学习成本在于 RxJS ,RxJS只是 Rx思想的JS实现。这里强烈推荐学习Rx编程思想, 她的学习收益比绝对让你想象不到。

RxJS不需要我们不断的手动去SharedService中检查数据是否产生了变更,而是在数据有变化时,主动将变动的数据推送给感兴趣的任何订阅者。

举个栗子:

我们有一份随时可能会发生变动的数据存在了服务A中,在没有使用RxJS(或是类似框架/库)的情况下,我们想要知道数据的变化, 我们可能会采用轮询的机制去不断的询问服务A,我们关心的数据是否发生了变化,如果变化了就做出相应的动作。我们处于一种盲目的主动状态。

更高级一点的实现,就是我们可能把服务A实现称为一个可被观察的对象,这样我们便能通过观察服务A的状态来获取数据的变动。

现在我们来使用RxJS实现,RxJS现在可以理解为更高级的观察者模式实现。当使用来RxJS之后,在服务A中的数据发生变化时,服务A会主动 将变动的数据推送给每一个感兴趣的‘消费者',这些消费者处于被动接受数据,自主处理数据的状态中。RxJS不同于普通观察者模式的地方在于, 它提供来一系列的数据操作方法,比如:filter, map等等。这样就便于我们更加精细的处理数据。

下面通过一段简单的示例代码来感受一下:

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class DataService {

 private data: any;
 private subject: Subject<any> = new Subject<any>();

 setData(data: any): void {
 this.data = data;
 this.subject.next(data);
 }

 getData(): Observable<any> {
 return this.subject.asObservable();
 }
}

上面的代码中,我们简单的实现了一个可观察的数据服务,下面我们来使用这个服务

import { Component, OnInit } from '@angular/core';

import { DataService } from './shared/Dataservice';

@Component({
 moduleId: module.id,
 selector: '<my-component></my-component>',
 templateUrl: '.name.component.html',
 providers: [DataService]
})
export class MyComponent implements OnInit {

 constructor(private dataService: DataService) {}

 ngOnInit() {
 // Will invoke data handler everytime
 // when other component use the setter this.dataService.setData()
 this.dataService.getData().subscribe(data => console.log(data));
 }
}

使用Angular底层提供的 @Input 和 @Output 装饰器来实现组件间的通信服务

新的Angular采用Web Component的方式进行组件的封装,并将组件间的组织关系设计称为树状结构。这为应用数据的流向,管理提供了良好的支持, 也使得我们可以在Angular应用中使用一些别的库,比如: Redux 思想。基于这些设计理念,Angular为指令提供了更为强大的功能,组件也是指令。

采用 @Input 修饰属性,实现 parent -> child 组件间的通信

下面的示例代码将展示如何设置一个组件的Input属性

import { Component, Input, OnInit } from '@angular/core';

@Component({
 moduleId: module.id,
 selector: 'child-component',
 template: `I'm {{ name }}`
})
export class ChildComponent implements OnInit {

 @Input() name: string;

 constructor() { }

 ngOnInit() { }
}

上面的代码中,我们实现了一个名叫ChildComponent的组件,这个组件的有一个采用@Input装饰器修饰的属性:name。

下面我们将展示如何使用这个这个组件,并为这个Input属性赋值。

import { Component, OnInit } from '@angular/core';
import { ChildComponent } from './child-component';

@Component({
 moduleId: module.id,
 selector: 'parent-component',
 template: `<child-component [name]="childName"></child-component>`,
 // This is unnecessary when installing ChildComponent within Root NgModule
 directives: [ChildComponent]
})
export class ParentComponent implements OnInit {

 private childName: string;

 constructor() { }

 ngOnInit() { 
 this.childName = 'StevenShen';
 }
}

上面的代码实现中,在父组件中,我们为子组件的Input属性设置了父组件中的特定值。关键点在如下片段:

<child-component [name]="childName"></child-component>

Angular在进行AOT操作时,会将特定的值注入给ChildComponent中。

如果你在CodePen,或是自己的本地实验上面的代码你会发现,和AngularJS的指令中采用'@', ‘=', ‘&'等修饰的属性不一样的地方。

当父组件中的childName发生变化时,ChildComponent中的name属性并没有感知到变化。这是怎么了,是不是感觉新版的Angular在 和我们开玩笑,wtf!!!内心的表情是这样的 ○| ̄|_ 。(感觉一篇学习笔记开始被写的画风突变了。。。)

将父组件的属性变化映射到子组件中

上一小节的实现,虽然在初始化子组件时,我们可以将父组件的值反馈到子组件中。但是,初始化完成后,父组件中相关属性的变化却不能被子组件感知。

这无疑是让我们内心崩溃的。为什么和AngularJS不一样了???别急,下面我们将来提供解决方案。

利用Angular提供的组件生命周期钩子函数ngOnChanges来监听输入属性值的变化

需要实现让子组件感知到父组件中相关属性的变化,我们需要对Angualr组件的生命周期有一定的了解,采用Angular提供的组件生命周期的钩子函数, 进行组件间数据的同步。(关于Angualr组件的生命周期,之后会有相关的学习笔记整理。到时候在加上链接。)这里直接上代码:

import { Component, Input, SimpleChanges } from '@angular/core';

@Component({
 moduleId: module.id,
 selector: 'child-component',
 template: `I'm {{ name }}`
})
export class ChildComponent {

 @Input() name: string;

 ngOnChanges(changes: SimpleChanges) {
 this.name = changes['childName'].currentValue;
 }
}

采用ES5中的getter和setter方法进行输入属性的监听

在ES5中,我们在定义一个对象的属性时,可以通过Object.defineProperty方法为一个对象的属性设置关联的getter和setter方法, 当我们进行这一操作后,以后该对象上的相关属性的读取和赋值操作,都会调用相应的getter/setter方法进行预处理或改变操作结果。

同样的道理,在Angular中,我们通过设置和使用一个输入属性的setter方法,便可以拦截到父组件中相关值的变化,并进行特定的数据处理。

这种方法比较直观,直接上代码:

父组件的代码实现:

import { Component } from '@angular/core';
@Component({
 moduleId: module.id,
 selector: 'name-parent',
 template: `
 <h2>Master controls {{names.length}} names</h2>
 <name-child *ngFor="let name of names" [name]="name"></name-child>
 `
})
export class ParentComponent {
 names = ['StevenShen', ' ', ' lei '];
}

子组件的代码实现

import { Component, Input } from '@angular/core';
@Component({
 moduleId: module.id,
 selector: 'name-child',
 template: `<h3>"{{name}}"</h3>`
})
export class ChildComponent {
 name: string = 'default name';

 @Input()
 set name(name: string) {
 this.name = (name && name.trim()) || 'default name';
 }

 get name() { return this.name; }
}

采用 @Output 修饰属性,实现 child -> parent 组件间的通信

新版的 Angular 中,子组件和父组件间的通信,采用来事件的机制。这样的设计有助于组件的复用和代码的解耦;
我们不需要过多的关心组件的具体实现,我们只需要知道一个组件它接收哪些数据,并产生哪些输出事件即可。

直接上代码直观了解一下:

@Component({
 moduleId: module.id,
 selector: 'child-component',
 template: `I'm {{ name }}`
})
export class ChildComponent {

 @Input() name: string;

 @Output() say: EventEmitter<boolean> = new EventEmitter<boolean>();

 ngOnChanges(changes: SimpleChange) {
 this.name = changes['childName'].currentValue;
 }

 speak() {
 this.say.emit(true);
 }
}

子组件变更完成后,我们来变更父组件的代码实现。

import { Component, OnInit } from '@angular/core';
import { ChildComponent } from './child-component';

@Component({
 moduleId: module.id,
 selector: 'parent-component',
 template: `<child-component [name]="childName" (say)="isChildSpeak($event)"></child-component>`,
 // This is unnecessary when installing ChildComponent within Root NgModule
 directives: [ChildComponent]
})
export class ParentComponent implements OnInit {

 private childName: string;

 constructor() { }

 ngOnInit() { 
 this.childName = 'StevenShen';
 }

 isChildSpeak(isIt: boolean) {
 console.log('The child speak status is %s', isIt ? 'ture' : 'false');
 }
}

这样一来就实现了父子组件间的通信了。

但是这样的实现存在一定的局限性:父组件不能使用数据绑定来读取子组件的属性或调用子组件的方法.

通过 @ViewChild 获取组件的控制器/模版进行组件间的通信

除开使用 @Input 和 @Output 修饰器搭配Angular的生命周期钩子函数进行组件间通信。 我们还可以采用@ViewChild来进行不同组件间的通信,而不仅仅局限于父子组件间的通信。同时,采用@ViewChild的方式, 我们可以获得更为精细的组件控制权限,比如在父组件中读取子组件的属性值或调用子组件的方法。我们依然采用上面的代码来进行改造。

对于ChildComponent组件的变更:

import { Component } from '@angular/core';

@Component({
 moduleId: module.id,
 selector: 'child-component',
 template: `I'm {{ name }}`
})
export class ChildComponent {
 public name: string;

 speak() {
 console.log('say something whitout EventEmitter');
 }
}

对于ParentComponent组件的变更:

import { Component, OnInit, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
import { ChildComponent } from './child-component.ts';

@Component({
 moduleId: module.id,
 selector: 'parent-component',
 // attention #childCmp tag
 template: `
 <child-component #childCmp></child-component>
 <button (click)="child.name = childName"></button>
 `,
 // This is unnecessary when installing ChildComponent within Root NgModule
 directives: [ ChildComponent ]
})

export class ParentComponent implements OnInit, AfterViewInit {

 @ViewChild('childCmp') childCmp: ElementRef;


 constructor() { }

 ngOnInit() { 
 this.childCmp.name = 'StevenShen';
 }

 ngAfterViewInit() {
 this.childCmp.speak();
 }
}

通过上面的代码改造,我们同样可以实现不同组件间的通信,而且这样的组件通信已经不仅仅局限于父子组件间的通信了。

总结

由于技术水平和时间原因,这篇文章完成得比较粗略。主要整理的都是自己在工作中实际使用到的一些方案。

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

Javascript 相关文章推荐
Javascript miscellanea -display data real time, using window.status
Jan 09 Javascript
javaScript parseInt字符转化为数字函数使用小结
Nov 05 Javascript
跟我一起学写jQuery插件开发方法(附完整实例及下载)
Apr 01 Javascript
基于Flowplayer打造一款免费的WEB视频播放器附源码
Sep 06 Javascript
jQuery+Ajax+PHP弹出层异步登录效果(附源码下载)
May 27 Javascript
网页中右键功能的实现方法之contextMenu的使用
Feb 20 Javascript
对于Javascript 执行上下文的全面了解
Sep 05 Javascript
详解vue中的computed的this指向问题
Dec 05 Javascript
JS继承定义与使用方法简单示例
Feb 19 Javascript
vue实现前端分页完整代码
Jun 17 Javascript
浅析VUE防抖与节流
Nov 24 Vue.js
JavaScript实现网页动态生成表格
Nov 25 Javascript
js的三种继承方式详解
Jan 21 #Javascript
微信小程序 天气预报开发实例代码源码
Jan 20 #Javascript
微信小程序 自定义对话框实例详解
Jan 20 #Javascript
Vue实例简单方法介绍
Jan 20 #Javascript
微信小程序 Toast自定义实例详解
Jan 20 #Javascript
JavaScript判断浏览器及其版本信息
Jan 20 #Javascript
JS中传递参数的几种不同方法比较
Jan 20 #Javascript
You might like
ThinkPHP中Session用法详解
2014/11/29 PHP
Yii2 如何在modules中添加验证码的方法
2017/06/19 PHP
ThinkPHP like模糊查询,like多匹配查询,between查询,in查询,一般查询书写方法
2018/09/26 PHP
Laravel 手动开关 Eloquent 修改器的操作方法
2019/12/30 PHP
PHP pthreads v3使用中的一些坑和注意点分析
2020/02/21 PHP
为开发者准备的10款最好的jQuery日历插件
2014/02/04 Javascript
jQuery中:empty选择器用法实例
2014/12/30 Javascript
jquery SweetAlert插件实现响应式提示框
2015/08/18 Javascript
jQuery实现鼠标经过时出现隐藏层文字链接的方法
2015/10/12 Javascript
跟我学习javascript的函数调用和构造函数调用
2015/11/16 Javascript
jquery ajax双击div可直接修改div中的内容
2016/03/04 Javascript
jQuery 获取屏幕高度、宽度的简单实现案例
2016/05/17 Javascript
关于function类中定义变量this的简单说明
2016/05/28 Javascript
javacript获取当前屏幕大小
2016/06/04 Javascript
AngularJS使用ng-repeat和ng-if实现数据的删选显示效果示例【适用于表单数据的显示】
2016/12/13 Javascript
JavaScript实现前端实时搜索功能
2020/03/26 Javascript
详解小程序缓存插件(mrc)
2018/08/17 Javascript
在vue项目中集成graphql(vue-ApolloClient)
2018/09/08 Javascript
python切换hosts文件代码示例
2013/12/31 Python
Django Web开发中django-debug-toolbar的配置以及使用
2018/05/06 Python
Python实现的批量修改文件后缀名操作示例
2018/12/07 Python
Python实现子类调用父类的初始化实例
2020/03/12 Python
Pytorch mask-rcnn 实现细节分享
2020/06/24 Python
python实现KNN近邻算法
2020/12/30 Python
HTML5通过调用canvas对象的getContext()方法来获取绘图环境
2014/06/23 HTML / CSS
介绍一下Java中标识符的命名规则
2014/02/03 面试题
2013年保送生自荐信格式
2013/11/20 职场文书
创先争优一句话承诺
2014/05/29 职场文书
2014班子成员自我剖析材料思想汇报
2014/10/01 职场文书
2016年端午节校园广播稿
2015/12/18 职场文书
党员理论学习心得体会
2016/01/21 职场文书
《活见鬼》教学反思
2016/02/24 职场文书
SpringBoot整合阿里云视频点播的过程详解
2021/12/06 Java/Android
MySql中的json_extract函数处理json字段详情
2022/06/05 MySQL
Django框架中表单的用法
2022/06/10 Python
MySQL索引失效十种场景与优化方案
2023/05/08 MySQL