Angular2组件间交互

       组件间交互简单来说就是让两个或多个组件之间共享信息。接下来我们就对Angular2组件间的交互做一个简单的解释。当然做好的文档还是官方文档:https://www.angular.cn/guide/component-interaction

一、通过@Input把父组件的属性绑定到子组件

       @Input注解是属性绑定,通常在父组件需要向子组件传递数据的时候使用。关于@Input你可以简单的理解为子组件创建的时候需要传递参数(当然子组件的创建指的是在父组件对应的html里面申明)。

有@Input那肯定就会对应的有一个@Output,@Output是用于子组件向父组件传递数据的时候触发事件。关于@Output我们会在下面讲到。

       @Input的使用简单的很,首先在子组件定义的时候我们先明确哪些属性是需要父组件传递过来的,给加上@Input注解就完事了。然后父组件通过模板语法把属性绑定到子组件上去就完事了。

       我们用一个非常简单的实例看下@Input的使用,父组件需要把Hero对象传递到子组件里面去。

子组件hero属性加上@Input()注解。

import {Component, Input} from '@angular/core';
import {Hero} from '../hero';

@Component({
  selector: 'app-data-child',
  template: `
    <p>我是子组件,父组件传递的值是:"{{hero.name}}"</p>
  `
})
export class DataChildComponent {

  // 该属性需要从父组件传递过来
  @Input() hero: Hero;

  constructor() { }
}

父组件通过[hero]="parentHero"把parentHero属性绑定到子组件上去

import {Component} from '@angular/core';
import {Hero} from '../hero';

@Component({
  selector: 'app-data-parent',
  styleUrls: ['./data-parent.component.css'],
  template: `
    <app-data-child [hero]="parentHero"></app-data-child>
    `
})
export class DataParentComponent {

  parentHero: Hero = new Hero();

  constructor() {
    this.parentHero.name = '我是父组件定义的';
  }
}

       @Input的使用就是这么的简单的,除了[hero]="parentHero"单向绑定,我们也可以使用[(hero)]="parentHero"双向绑定。

1.1、通过setter截听输入属性值的变化

       使用输入属性的 setter、getter方法来拦截父组件中值的变化,一边是在setter函数里面做一些相应的处理,然后getter函数里面返回。 我们还是继续在上面的基础上做一个简单的修改。对子组件做一个简单的修改把父组件传递过来的Hero对象里面的名字都改成大写。

import {Component, Input} from '@angular/core';
import {Hero} from '../hero';

@Component({
  selector: 'app-data-child',
  template: `
    <p>我是子组件,父组件传递的值是:"{{hero.name}}"</p>
  `
})
export class DataChildComponent {

  private _hero: Hero;

  // 该属性需要从父组件传递过来,我们把Hero对象里面的name改成大写
  @Input()
  set hero(hero: Hero) {
    // 把父组件传递过来的数据装换成大写
    const name = (hero.name && hero.name.toUpperCase()) || '<no name set>';
    this._hero = new Hero();
    this._hero.name = name;
  }

  get hero(): Hero {
    return this._hero;
  }

  constructor() {
  }
}

1.2、通过ngOnChanges()钩子来拦截输入属性值的变化

       使用OnChanges生命周期钩子接口的ngOnChanges() 方法来监测输入属性值的变化并做出回应。当输入属性值变化的时候会回调ngOnChanges()方法。

ngOnChanges()钩子:当Angular(重新)设置数据绑定输入属性时响应。 该方法接受当前和上一属性值的SimpleChanges对象
当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在ngOnInit()之前。

       关于通过ngOnChanges()钩子来拦截属性值的变化。想强调的一点就是。数据变化指的是属性指向的地址发生改变才会回调ngOnChanges()函数。所以对于一些自定义的数据类型(class)要特别小心。

       我们还是用一个简单的例子来说明怎么通过通过ngOnChanges()钩子来拦截输入属性值的变化。子组件需要父组件传递一个string属性过来,通过ngOnChanges()钩子拦截到属性的变化,把数据都改为大写的。

子组件,把父组件传递过来的数据改为大写

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

@Component({
  selector: 'app-data-child',
  template: `
    <p>我是子组件,父组件传递的值是:"{{inputString}}"</p>
  `
})
export class DataChildComponent implements OnChanges {

  // 该属性需要从父组件传递过来
  @Input()
  inputString: string;

  // 拦截inputString的变化,并且把他变成大写
  ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
    for (const propName in changes) {
      if (changes.hasOwnProperty(propName)) {
        const changedProp = changes[propName];
        const to = JSON.stringify(changedProp.currentValue);
        // 我们这里只想要inputString属性的变化
        if (propName === 'inputString') {
          if (changedProp.isFirstChange()) {
            // 第一次数据设置
          } else {
            // 不是第一次
          }
          this.inputString = to.toUpperCase();
        }
      }
    }
  }
}

父组件相关代码

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

@Component({
  selector: 'app-data-parent',
  styleUrls: ['./data-parent.component.css'],
  template: `
    <app-data-child [(inputString)]="inputString"></app-data-child>
    <button (click)="onValueChangeClick()">改变值</button>
    `
})
export class DataParentComponent {

  inputString: string;

  constructor() {
    this.inputString = 'nihao';
  }

  onValueChangeClick() {
    this.inputString = 'change';
  }
}

二、通过@Output让父组件监听子组件的事件

       通过@Output注解指明一个输出属性(EventEmitter类型)。通过在子组件里面暴露一个EventEmitter属性,当事件发生时,子组件利用该属性 emits(向上弹射)事件。父组件绑定到这个事件属性,并在事件发生时作出回应。子组件的EventEmitter属性是一个输出属性,所以需要带有@Output装饰器。这一部分可以类比JAVA里面的接口的使用。相当于在父组件里面实现接口,在子组件里面调用接口。

       通过@Output让父组件监听子组件的事件的使用也非常简单。我们分为三个步骤:

  1. 子组件里面明确我们要把什么事件抛出去给父组件(定义一个@Output()注解修饰的EventEmitter属性),
  2. 抛出事件。子组件做了什么动作之后抛出事件。调用EventEmitter属性的emit()方法抛出事件。
  3. 父组件里面绑定事件处理器,当子组件有事件抛出来的时候会调用父组件的处理函数。

       我还是以一个非常简单的例子来说明,我们在子组件里面定义一个button,当button点击的时候。把事件抛给父组件。统计点击的次数。

子组件相关代码

import {Component, EventEmitter, OnChanges, Output} from '@angular/core';

@Component({
  selector: 'app-data-child',
  template: `
    <button (click)="vote(true)">点击</button>
  `
})
export class DataChildComponent {

  // @Output定义一个准备回调父组件的事件EventEmitter也是可以传递参数的
  @Output() voted = new EventEmitter<boolean>();


  vote(agreed: boolean) {
    // 把事件往上抛出去,可以带参数
    this.voted.emit(agreed);
  }
}

父组件相关代码

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

@Component({
  selector: 'app-data-parent',
  styleUrls: ['./data-parent.component.css'],
  template: `
    <p>点击 {{clickCount}} 次</p>
    <app-data-child (voted)="onVoted($event)"></app-data-child>
  `
})
export class DataParentComponent {

  clickCount = 0;

  /**
   * 子组件抛上来的事件
   */
  onVoted(agreed: boolean) {
    this.clickCount++;
  }
}

三、父子组件通过本地变量互动

       在父组件模板里,新建一个本地变量来代表子组件(指向子组件)。然后利用这个变量来读取子组件的属性和调用子组件的方法。

       一个本地变量互动的简单实例,在父组件里面有两个按钮:一个开始按钮、一个结束按钮。调用子组件里面的开始和结束方法。在下面代码中chiild指向的就是子组件,然后通过chiild来调用子组件里面的方法。

子组件相关代码

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

@Component({
  selector: 'app-data-child',
  template: `<p>{{message}}</p>`,
  styleUrls: ['./data-child.component.css']
})
export class DataChildComponent {

  message = '初始值';

  onStart(): void {
    this.message = '父组件告诉开始了';
  }

  onEnd(): void {
    this.message = '父组件告诉结束了';
  }
}

父组件相关代码

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

@Component({
  selector: 'app-data-parent',
  template: `
    <button (click)="chiild.onStart()">开始</button>
    <button (click)="chiild.onEnd()">结束</button>
    <app-data-child #chiild></app-data-child>
  `,
  styleUrls: ['./data-parent.component.css']
})
export class DataParentComponent {
}

父子组件通过本地变量互动缺点是,本地变量的作用范围只是html(模板)文件里面。在ts文件里面没办法使用。并且只能是单向的,只能在父组件的模板里面调用子组件的属性或者方法。

四、父组件通过@ViewChild()调用子组件里面的属性方法

       父子组件通过本地变量互动的缺点是变量只能在模板里面使用,没办法在ts文件代码里面使用。@ViewChild()就是来解决这个办法的。
当父组件类需要访问子组件属性或者方法的时候,可以把子组件作为 ViewChild,注入到父组件里面。

       我们还是对上面的例子做一个简单的修改,父组件告诉子组件开始和结束。

子组件代码

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

@Component({
  selector: 'app-data-child',
  template: `<p>{{message}}</p>`,
  styleUrls: ['./data-child.component.css']
})
export class DataChildComponent {

  message = '初始值';

  onStart(): void {
    this.message = '父组件告诉开始了';
  }

  onEnd(): void {
    this.message = '父组件告诉结束了';
  }
}

父组件代码

import {Component, ViewChild} from '@angular/core';
import {DataChildComponent} from './data-child.component';

@Component({
  selector: 'app-data-parent',
  template: `
    <button (click)="start()">开始</button>
    <button (click)="end()">结束</button>
    <app-data-child #chiild></app-data-child>
  `,
  styleUrls: ['./data-parent.component.css']
})
export class DataParentComponent {

  @ViewChild(DataChildComponent)
  private childComponent: DataChildComponent;

  start(): void {
    this.childComponent.onStart();
  }

  end(): void {
    this.childComponent.onEnd();
  }

}

       当在一个父组件里面有同一个子组件多个的时候,又应该怎么处理呢。

import {Component, ViewChild} from '@angular/core';
import {DataChildComponent} from './data-child.component';

@Component({
  selector: 'app-data-parent',
  template: `
    <button (click)="start()">开始</button>
    <button (click)="end()">结束</button>
    <app-data-child #chiild1></app-data-child>
    <app-data-child #chiild2></app-data-child>
  `,
  styleUrls: ['./data-parent.component.css']
})
export class DataParentComponent {

  @ViewChild('chiild1')
  private childComponent1: DataChildComponent;
  @ViewChild('chiild2')
  private childComponent2: DataChildComponent;

  start(): void {
    this.childComponent1.onStart();
    this.childComponent2.onStart();
  }

  end(): void {
    this.childComponent1.onEnd();
    this.childComponent2.onEnd();
  }

}

五、父子组件通过服务通讯

       父组件和它的子组件共享同一个服务(父组件和子组件使用的是同一个服务实例,说白了就是同一个对象)。父子组件间通过发布订阅的消息机制来实现通讯,一个发布消息,一个订阅消息。说白了就是观察值模式。

       我们用一个父子组件的相互通信来做一个简单的说明。MissionService就是中间服务,MissionService里面有两个bservable属性。用来发布订阅消息的。在一个组件里面发布另一个组件里面订阅。

service->MissionService

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

@Injectable()
export class MissionService {

  // Subject可以看着是一个桥梁或者代理
  private childToParentSubject = new Subject<string>();
  private parentToChildSubject = new Subject<string>();

  // 定义观察者(Observable变量在定义的时候都会在后面加上$)
  childToParentObservable$ = this.childToParentSubject.asObservable();
  parentToChildObservable$ = this.parentToChildSubject.asObservable();

  // 父组件给子组件发送消息,这样parentToChildObservable$就能收到消息
  parentSendMessageToChild(mission: string) {
    this.parentToChildSubject.next(mission);
  }

  // 子组件给父组件发送消息,这样childToParentObservable$就能收到消息
  childSendMessageToParent(astronaut: string) {
    this.childToParentSubject.next(astronaut);
  }
}

子组件对应代码

import {Component, OnDestroy} from '@angular/core';
import {MissionService} from './mission.service';
import {Subscription} from 'rxjs';

@Component({
  selector: 'app-service-child',
  template: `
    <p>收到父组件的消息: {{message}}</p>
    <button (click)="sendMessage()">发送消息</button>
  `,
  styleUrls: ['./service-child.component.css']
})
export class ServiceChildComponent implements OnDestroy {
  message = '';
  subscription: Subscription;

  constructor(private missionService: MissionService) {
    // 订阅消息
    this.subscription = missionService.parentToChildObservable$.subscribe(
      mission => {
        this.message = mission;
      });
  }

  // 发送消息
  sendMessage() {
    this.missionService.childSendMessageToParent('我是子组件给你发消息了哈');
  }

  ngOnDestroy() {
    // 组件销毁的时候,subscription需要取消订阅
    this.subscription.unsubscribe();
  }

}

父组件对应代码

import {Component, OnDestroy} from '@angular/core';
import {MissionService} from './mission.service';
import {Subscription} from 'rxjs';

@Component({
  selector: 'app-service-parent',
  template: `
    <p>收到子组件的消息: {{message}}</p>
    <button (click)="sendMessage()">发送消息</button>
    <app-service-child></app-service-child>
  `,
  styleUrls: ['./service-parent.component.css'],
  providers: [MissionService]
})
export class ServiceParentComponent implements OnDestroy{

  message = '';
  subscription: Subscription;

  constructor(private missionService: MissionService) {
    // 订阅消息,当数据改变的时候,会调用到改函数
    this.subscription = missionService.childToParentObservable$.subscribe(
      astronaut => {
        this.message = astronaut;
      });
  }

  sendMessage() {
    this.missionService.parentSendMessageToChild('我是父组件给你发消息了哈');
  }

  ngOnDestroy(): void {
    // 取消订阅
    this.subscription.unsubscribe();
  }


}


       本文涉及到的所有例子下载地址:DEMO下载地址。demo里面的例子可能会稍微复杂一点。


       关于组件间的交互,我们就讲这么多,貌似看起来也不是很复杂,咱也是个初学者(我是做android,以前也没有接触过前段方面的知识)。这里我想在说一句最好的文档还是官方文档,强烈推荐大家看官方文档 https://www.angular.cn/guide/component-interaction#parent-interacts-with-child-via-emlocal-variableem

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 学习资料来自 Angular.cn 与 Angular.io。 模板语法 在线例子 在 Angular 中,组件扮...
    小镭Ra阅读 3,617评论 0 3
  • 卡夫卡的短篇,群中有个改写作业,我便找出来读一读。读至开头就发现,我很多年前就已读过。然而那时候气盛,读完只是觉得...
    苹果与烤翅阅读 164评论 0 2
  • 不同的对象引用,本质是体现对象的可达性和垃圾回收。String 不可变,Immutable 类,被声明成为 fin...
    那有一只羊阅读 420评论 0 0
  • 今天中午吃饱饭,我妈妈给我买的太阳能板机器人制作的玩具到了,我非常的高兴,因为我特别喜欢拼装的东西。我和我...
    碎片幻影阅读 687评论 0 0