详解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 相关文章推荐
DHTML Slide Show script图片轮换
Mar 03 Javascript
仿百度输入框智能提示的js代码
Aug 22 Javascript
jQuery实现加入购物车飞入动画效果
Mar 14 Javascript
Angularjs实现分页和分页算法的示例代码
Dec 23 Javascript
vue动态生成dom并且自动绑定事件
Apr 19 Javascript
详解React native全局变量的使用(跨组件的通信)
Sep 07 Javascript
前端常见跨域解决方案(全)
Sep 19 Javascript
angular.js4使用 RxJS 处理多个 Http 请求
Sep 23 Javascript
vue-cli V3.0版本的使用详解
Oct 24 Javascript
浅析JavaScript异步代码优化
Mar 18 Javascript
vue实现导航标题栏随页面滚动渐隐渐显效果
Mar 12 Javascript
vue实现折线图 可按时间查询
Aug 21 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
用PHP+java实现自动新闻滚动窗口
2006/10/09 PHP
PHP isset()与empty()的使用区别详解
2010/08/29 PHP
php和vue配合使用技巧和方法
2019/05/09 PHP
超级简单的图片防盗(HTML),好用
2007/04/08 Javascript
window.open以post方式将内容提交到新窗口
2012/12/26 Javascript
使用Grunt.js管理你项目的应用说明
2013/04/24 Javascript
再谈Jquery Ajax方法传递到action(补充)
2014/05/12 Javascript
jquery调整表格行tr上下顺序实例讲解
2016/01/09 Javascript
基于Three.js插件制作360度全景图
2016/11/29 Javascript
浅谈angular2路由预加载策略
2017/10/04 Javascript
vue脚手架及vue-router基本使用
2018/04/09 Javascript
在HTML文档中嵌入JavaScript的四种方法
2018/05/07 Javascript
详解JavaScript 中 if / if...else...替换方式
2018/07/15 Javascript
Vue响应式原理Observer、Dep、Watcher理解
2019/06/06 Javascript
微信小程序 腾讯地图显示偏差问题解决
2019/07/27 Javascript
d3.js实现图形拖拽
2019/12/19 Javascript
Python虚拟环境Virtualenv使用教程
2015/05/18 Python
python opencv摄像头的简单应用
2019/06/06 Python
django用户登录验证的完整示例代码
2019/07/21 Python
python爬虫 urllib模块反爬虫机制UA详解
2019/08/20 Python
Python Selenium 设置元素等待的三种方式
2020/03/18 Python
解决Jupyter Notebook开始菜单栏Anaconda下消失的问题
2020/04/13 Python
有关pycharm登录github时有的时候会报错connection reset的问题
2020/09/15 Python
python eventlet绿化和patch原理
2020/11/21 Python
python des,aes,rsa加解密的实现
2021/01/16 Python
CSS3 text shadow字体阴影效果
2016/01/08 HTML / CSS
Microsoft Advertising美国:微软搜索广告
2019/05/01 全球购物
应届本科生推荐信范文
2013/12/25 职场文书
优秀大学生的自我评价
2014/01/16 职场文书
产品开发计划书
2014/04/27 职场文书
小学生五年级大队长竞选发言稿
2014/09/12 职场文书
党员年终个人总结
2015/02/14 职场文书
校长师德表现自我评价
2015/03/05 职场文书
2015年技术工作总结范文
2015/04/20 职场文书
赞助商致辞
2015/07/30 职场文书
使用python绘制分组对比柱状图
2022/04/21 Python