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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,992評論 19 139
  • 學習資料來自 Angular.cn 與 Angular.io。 模板語法 在線例子 在 Angular 中,組件扮...
    小鐳Ra閱讀 3,804評論 0 3
  • 卡夫卡的短篇,群中有個改寫作業,我便找出來讀一讀。讀至開頭就發現,我很多年前就已讀過。然而那時候氣盛,讀完只是覺得...
    蘋果與烤翅閱讀 202評論 0 2
  • 不同的對象引用,本質是體現對象的可達性和垃圾回收。String 不可變,Immutable 類,被聲明成為 fin...
    那有一只羊閱讀 447評論 0 0
  • 今天中午吃飽飯,我媽媽給我買的太陽能板機器人制作的玩具到了,我非常的高興,因為我特別喜歡拼裝的東西。我和我...
    碎片幻影閱讀 822評論 0 0