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,SimpleChanges } from '@angular/core';

@Component({
    moduleId: module.id,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,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,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,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,// 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();
    }
}

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

总结

由于技术水平和时间原因,这篇文章完成得比较粗略。主要整理的都是自己在工作中实际使用到的一些方案。有不足之处或是更好的方案,希望大家在评论区指出。

Angualr 组件间通信的更多相关文章

  1. HTML5 input新增type属性color颜色拾取器的实例代码

    type 属性规定 input 元素的类型。本文较详细的给大家介绍了HTML5 input新增type属性color颜色拾取器的实例代码,感兴趣的朋友跟随脚本之家小编一起看看吧

  2. 移动HTML5前端框架—MUI的使用

    这篇文章主要介绍了移动HTML5前端框架—MUI的使用的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. 使用placeholder属性设置input文本框的提示信息

    这篇文章主要介绍了使用placeholder属性设置input文本框的提示信息,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下

  4. 详解html5 postMessage解决跨域通信的问题

    这篇文章主要介绍了详解html5 postMessage解决跨域通信的问题的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. Bootstrap File Input文件上传组件

    这篇文章主要介绍了Bootstrap File Input文件上传组件,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. 详解使用postMessage解决iframe跨域通信问题

    这篇文章主要介绍了详解使用postMessage解决iframe跨域通信问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  7. HTML5给汉字加拼音收起展开组件的实现代码

    HTML 规范是 W3C 与 WHATWG 合作共同产出的,HTML5 因此也不例外,这篇文章主要介绍了HTML5给汉字加拼音收起展开组件的实现代码,需要的朋友可以参考下

  8. HTML5调用手机发短信和打电话功能

    这篇文章主要介绍了HTML5调用手机发短信和打电话功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. HTML5中input输入框默认提示文字向左向右移动的示例代码

    这篇文章主要介绍了HTML5中input输入框默认提示文字向左向右移动,本文通过实例代码给大家介绍的非常详细对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  10. 真正的iOS设备和Watch Simulator可以进行通信以进行测试

    我想为现有的iOS应用创建一个手表应用.但我处于一种情况,我没有苹果手表,我现有的iOS应用程序只能在不在模拟器上的真实设备上运行.是否可以在iPhone设备上运行应用程序并在手表模拟器中测试我的手表应用程序?解决方法至少在目前,不可能配对真正的iPhone和Watch模拟器.我得出这个结论有三个原因:>Watch模拟器在安装过程中自动与iPhone模拟器配对.>根本无法从界面取消配对Watch模拟器.>在模拟器上无法访问蓝牙以与真实设备进行通信.这是一个proof.

随机推荐

  1. Angular2 innerHtml删除样式

    我正在使用innerHtml并在我的cms中设置html,响应似乎没问题,如果我这样打印:{{poi.content}}它给了我正确的内容:``但是当我使用[innerHtml]=“poi.content”时,它会给我这个html:当我使用[innerHtml]时,有谁知道为什么它会剥离我的样式Angular2清理动态添加的HTML,样式,……

  2. 为Angular根组件/模块指定@Input()参数

    我有3个根组件,由根AppModule引导.你如何为其中一个组件指定@input()参数?也不由AppModalComponent获取:它是未定义的.据我所知,你不能将@input()传递给bootstraped组件.但您可以使用其他方法来做到这一点–将值作为属性传递.index.html:app.component.ts:

  3. angular-ui-bootstrap – 如何为angular ui-bootstrap tabs指令指定href参数

    我正在使用角度ui-bootstrap库,但我不知道如何为每个选项卡指定自定义href.在角度ui-bootstrap文档中,指定了一个可选参数select(),但我不知道如何使用它来自定义每个选项卡的链接另一种重新定义问题的方法是如何使用带有角度ui-bootstrap选项卡的路由我希望现在还不算太晚,但我今天遇到了同样的问题.你可以通过以下方式实现:1)在控制器中定义选项卡href:2)声明一个函数来改变控制器中的散列:3)使用以下标记:我不确定这是否是最好的方法,我很乐意听取别人的意见.

  4. 离子框架 – 标签内部的ng-click不起作用

    >为什么标签标签内的按钮不起作用?>但是标签外的按钮(登陆)工作正常,为什么?>请帮我解决这个问题.我需要在点击时做出回复按钮workingdemo解决方案就是不要为物品使用标签.而只是使用divHTML

  5. Angular 2:将值传递给路由数据解析

    我正在尝试编写一个DataResolver服务,允许Angular2路由器在初始化组件之前预加载数据.解析器需要调用不同的API端点来获取适合于正在加载的路由的数据.我正在构建一个通用解析器,而不是为我的许多组件中的每个组件设置一个解析器.因此,我想在路由定义中传递指向正确端点的自定义输入.例如,考虑以下路线:app.routes.ts在第一个实例中,解析器需要调用/path/to/resourc

  6. angularjs – 解释ngModel管道,解析器,格式化程序,viewChangeListeners和$watchers的顺序

    换句话说:如果在模型更新之前触发了“ng-change”,我可以理解,但是我很难理解在更新模型之后以及在完成填充更改之前触发函数绑定属性.如果您读到这里:祝贺并感谢您的耐心等待!

  7. 角度5模板形式检测形式有效性状态的变化

    为了拥有一个可以监听其包含的表单的有效性状态的变化的组件并执行某些组件的方法,是reactiveforms的方法吗?

  8. Angular 2 CSV文件下载

    我在springboot应用程序中有我的后端,从那里我返回一个.csv文件WheniamhittingtheURLinbrowsercsvfileisgettingdownloaded.现在我试图从我的角度2应用程序中点击此URL,代码是这样的:零件:服务:我正在下载文件,但它像ActuallyitshouldbeBook.csv请指导我缺少的东西.有一种解决方法,但您需要创建一个页面上的元

  9. angularjs – Angular UI-Grid:过滤后如何获取总项数

    提前致谢:)你应该避免使用jQuery并与API进行交互.首先需要在网格创建事件中保存对API的引用.您应该已经知道总行数.您可以使用以下命令获取可见/已过滤行数:要么您可以使用以下命令获取所选行的数量:

  10. angularjs – 迁移gulp进程以包含typescript

    或者我应该使用tsc作为我的主要构建工具,让它解决依赖关系,创建映射文件并制作捆绑包?

返回
顶部