Angular2 父子组件通信方式

  • Angular2官方文档对组件交互这块有详细的介绍-->文档--组件之间的交互。按文档介绍,组件间交互的方式一共有4种,包括:
  1. 通过输入型绑定把数据从父组件传到子组件(@Input decoration);子组件暴露一个EventEmitter属性(@Output decoration),当事件发生时,利用该属性emits向父组件发射事件。
  2. 父组件与子组件通过本地变量互动。(# var
  3. 父组件调用@ViewChild
  4. 父组件和子组件通过服务来通讯。
  • 我在这里只总结、详细介绍3种我在项目中使用过的方法,看完本文大概能做到如下的效果:
image.png
  • 创建项目,项目结构如下:
image.png

通过@Input@Output装饰器进行父、子组件间的通信

  • @Input:该属性绑定用于父组件向子组件传递数据。子组件可以通过以下两种方法截取属性的变更:
    • 使用一个输入属性的setter,以拦截父组件中值得变化。
    • 通过ngOnchanges()来截听输入属性值的变化。
  • @Output:该数据绑定用于子组件向父组件传递数据和事件。
<!--parent.component.html-->
<div style="width: 1000px;margin: auto">
<div class="card" style="width: 500px;float: left">
  <div class="card-header">
    父组件
  </div>
  <div class="card-body">
    <h5 class="card-title">父组件</h5>
    <div class="form-group">
      <label for="input">父组件输入:</label>
      <input type="text"
             class="form-control"
             id="input"
             placeholder="Input something"
             [(ngModel)]="parentPrint"  
      >
      <label for="output">父组件输出:</label>
      <input type="text"
             class="form-control"
             id="output"
             placeholder="Output something"
             [(ngModel)]="contentFromChild"
      >
    </div>
  </div>
</div>
<app-child
  [fromParent]="parentPrint"
  (fromChild)="fromChild($event)"
></app-child>
</div>

<!--child.component.html-->
<div class="card" style="width: 500px;">
  <div class="card-header">
    子组件
  </div>
  <div class="card-body">
    <h5 class="card-title">子组件</h5>
    <div class="form-group">
      <label for="input">子组件输入:</label>
      <input type="text"
             class="form-control"
             id="input"
             placeholder="Input something"
             [(ngModel)]="contentFromChild"
      >
      <label for="output">子组件输出:</label>
      <input type="text"
             class="form-control"
             id="output"
             placeholder="Output something"
             [(ngModel)]="fromParent"
      >
    </div>
    <button  class="btn btn-primary" (click)="clickChild()">Output方式</button>
  </div>
</div>
  • 效果如下:(1、父组件输入,子组件可同步输出;2、子组件输入需要(3、)点击按钮触发发射事件,将数据传送给父组件。)
image.png
  • @Input:父组件输入的同时,子组件能同步获取数据进行显示。核心代码如下:
//父组件
parentPrint: any;           //ts中,声明一个变量
[(ngModel)]="parentPrint"   //html中,绑定变量,获取用户输入
//html中,将数据传给子组件
<app-child [fromParent]="parentPrint"></app-child> 
//子组件
@Input() fromParent;        //ts中,用于直接接收从父组件获取的数据
[(ngModel)]="fromParent"    //html中,用于显示数据
  • 通过setter截听输入属性值的变化,在子组件中声明一个私有变量来获取父组件传递过来的数据,从而屏蔽上层获取下层信息。(简单一点就是不让父组件知道子组件用什么东西去接收传过来的数据)通过这种方法也可以获得同样的效果。
//子组件
 private _fromParent: any;      //私有变量,通过setter获取父组件的数据
@Input()                        //通过setter获取父组件的数据
  set fromParent(fromParent: any) {
    this._fromParent = fromParent;
  }
  get fromParent(): any {
    return this._fromParent;
  }
  • @Output:父组件接收子组件的数据时,子组件暴露一个EventEmitter属性,当事件发生时,子组件利用该属性emits(向上弹射)事件。父组件绑定到这个事件属性,并在事件发生时作出回应。核心代码如下:
//子组件
@Output() fromChild = new EventEmitter<any>();  //暴露一个输出属性

<button  class="btn btn-primary" (click)="clickChild()">Output方式</button> 
 //触发发射函数,将数据发送给父组件
  clickChild() {
    console.log('click child' , this.contentFromChild);
    this.fromChild.emit(this.contentFromChild);
  }

//父组件
[(ngModel)]="contentFromChild"  //绑定输出子组件的数据
//使用子组件,绑定事件属性
<app-child
  [fromParent]="parentPrint"
  (fromChild)="fromChild($event)"
></app-child>
//事件处理函数
 fromChild(event) {
   console.log(event);
   this.contentFromChild = event;
 }

父组件通过调用@ViewChild()来获取子组件的数据

  • 如果父组件的类需要读取子组件的属性和值或调用子组件的方法时,就可以把子组件作为ViewChild,注入到父组件里面。ViewChild顾名思义就是可以看见子组件里面的属性和方法。
<!--parent.component.html-->
<div style="width: 1000px;margin: auto">
<div class="card" style="width: 500px;float: left">
  <div class="card-header">
    父组件
  </div>
  <div class="card-body">
    <h5 class="card-title">父组件</h5>
    <div class="form-group">
      <label for="viewoutput">ViewChild父组件输出:</label>
      <input type="text"
             class="form-control"
             id="viewoutput"
             placeholder="ViewChild父组件输出"
             [(ngModel)]="viewOutput"
      >
    </div>
    <button class="btn btn-primary" (click)="clickView()">ViewChild方式</button>
  </div>
</div>
<app-child></app-child>
</div>
<!--child.component.html-->
<div class="card" style="width: 500px;">
  <div class="card-header">
    子组件
  </div>
  <div class="card-body">
    <h5 class="card-title">子组件</h5>
    <div class="form-group">
      <label for="input">子组件输入:</label>
      <input type="text"
             class="form-control"
             id="input"
             placeholder="Input something"
             [(ngModel)]="contentFromChild"
      >
    </div>
  </div>
</div>

  • 效果如下:
image.png
  • 父组件核心代码:
//ts
@ViewChild(ChildComponent)                  // 使用viewChild导入引用
private childComponent: ChildComponent;     // 将子组件注入到私有属性
//获取子组件数据并显示
clickView() {
    //直接获取子组件的属性
    this.viewOutput = this.childComponent.contentFromChild;
  }

//html
[(ngModel)]="viewOutput"
 <button class="btn btn-primary" (click)="clickView()">ViewChild方式</button>

父组件和子组件通过服务来通讯

  • 父组件和它的子组件共享同一个服务,利用该服务在家庭内部实现双向通讯。
<!--parent.component.html-->
<div style="width: 1000px;margin: auto">
<div class="card" style="width: 500px;float: left">
  <div class="card-header">
    父组件
  </div>
  <div class="card-body">
    <h5 class="card-title">父组件</h5>
    <div class="form-group">
      <label for="serviceoutput">父组件服务输入:</label>
      <input type="text"
             class="form-control"
             id="serviceoutput"
             placeholder="服务输入"
             [(ngModel)]="serviceInput"
      >
    </div>
    <button class="btn btn-primary" (click)="clickService()">Service方式</button>
  </div>
</div>
<app-child></app-child>
</div>

<!--child.component.html-->
<div class="card" style="width: 500px;">
  <div class="card-header">
    子组件
  </div>
  <div class="card-body">
    <h5 class="card-title">子组件</h5>
    <div class="form-group">
      <label for="serviceoutput">子组件服务输入:</label>
      <input type="text"
             class="form-control"
             id="serviceoutput"
             placeholder="服务输入"
             [(ngModel)]="serviceInput"
      >
    </div>
    <button class="btn btn-primary" (click)="clickService()">Service方式</button>
  </div>
</div>

//服务
//meditor.service.ts
import {Injectable} from '@angular/core';
import {Subject} from 'rxjs/Subject';
import {Observable} from 'rxjs/Observable';

@Injectable()
export class MeditorService {
  private subject = new Subject<MeditorMsg>();
  constructor() {}
  // 获取订阅者
  public getObservable(): Observable<MeditorMsg> {
    return this.subject.asObservable();
  }
  // 推送信息
  public push(msg: MeditorMsg) {
    this.subject.next(msg);
  }
}
// 中间者信息
export interface MeditorMsg {
  id: string;
  body: any;
}
  • 效果如下:
image.png

  • 父子组件的核心代码类似,在构造函数中将该服务实例注入到自身,父子组件都有一个唯一的id。无论是父组件还是子组件调用push()方法推送数据,双方都能接收到数据,这时候就要根据id来判断是要父组件使用数据还是子组件使用数据。核心代码如下:
subscription: Subscription = null;  //初始化一个订阅对象
//子组件构造函数,用于监听数据推送
constructor(
    private meditor: MeditorService
  ) {
    this.subscription = meditor.getObservable().subscribe(
      msg => {
        console.log(msg);
        if (msg.id === 'parent') {      //id为parent,获取父组件数据
          this.serviceInput = msg.body;
        }
      }
    );
  }
// 子组件将数据推送到中间着,给订阅者
clickService() {
    this.meditor.push({id: 'parent', body: this.serviceInput});
  }
//父组件构造函数,用于监听数据推送
constructor(
    private meditor: MeditorService
  ) {
    this.subscription = meditor.getObservable().subscribe(
      msg => {
        console.log(msg);
        if (msg.id === 'child') {       //id为child,获取子组件数据
          this.serviceInput = msg.body;
        }
      }
    );
  }
// 父组件将数据推送到中间着,给订阅者
clickService() {
    this.meditor.push({id: 'parent', body: this.serviceInput});
  }
  • 我上面写的还不是很完善,就是在生命周期结束前,也就是在onDestroy周期中,要取消订阅

​ 以上,就是最近在使用的组件交互的总结。个人觉得通过服务来交互的可扩展性更强。例如,我们项目中用到了一个动态显示的侧栏,不同时期点击显示侧栏要显示不同的东西。这个时候把侧栏作为父组件,子组件作为消息的一部分传递给父组件,父组件根据子组件名动态生成模板,显示在侧栏上面。说了这么多废话大概就是下图的意思:

image.png

最后附上demo源码:父子组件交互demo

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,188评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,464评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,562评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,893评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,917评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,708评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,430评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,342评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,801评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,976评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,115评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,804评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,458评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,008评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,135评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,365评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,055评论 2 355

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,665评论 18 139
  • 组件基础 组件用来包装特定的功能,应用程序的有序运行依赖于组件之间的协同工作。组件是angular应用的最小逻辑单...
    oWSQo阅读 1,371评论 0 0
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,180评论 25 707
  • 课文 Most of us have formed an unrealistic picture of life ...
    claresun阅读 324评论 0 0
  • 2017年就要结束了,进行一个简单的总结,并对新的即将到来的2018年做一下展望。 现在已经完全想不起来当时过年时...
    龙猫不是猫吧阅读 281评论 0 0