Angular學習(三):元件-元件間的通訊

大喻喻發表於2019-05-09

父元件傳值給子元件

一、生成父元件和子元件

  • 子元件HeroChildComponent中兩個輸入型屬性hero和masterName,通常帶@Input修飾符
  • 第二個 @Input 為子元件的屬性名 masterName 指定一個別名 master(不推薦為起別名,請參見風格指南).
  1. hero-parent.component.html
<app-hero-child *ngFor="let hero of heroes"
  [hero]="hero"
  [master]="master">
</app-hero-child>
複製程式碼
  1. hero-child.component.ts
export class HeroChildComponent {
  @Input() hero: Hero;
  @Input('master') masterName: string;
}
複製程式碼

二、通過 setter 截聽輸入屬性值的變化

可以使用一個輸入屬性的 setter,以攔截父元件中值的變化,並做處理。

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

@Component({
  selector: 'app-name-child',
  template: '<h3>"{{name}}"</h3>'
})
export class NameChildComponent {
  private _name = '';

  @Input()
    set name(name: string) {
      // trim掉name的空格,若為空則替換成預設字串
      this._name = (name && name.trim()) || '<no name set>';
    }
    get name(): string { return this._name; }
}
複製程式碼

三、通過ngOnChanges()來截聽輸入屬性值的變化

當需要監視多個、互動式輸入屬性的時候,本方法比用屬性的 setter 更合適。

ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
    let log: string[] = [];
    for (let propName in changes) {
      let changedProp = changes[propName];
      let to = JSON.stringify(changedProp.currentValue);
      if (changedProp.isFirstChange()) {
        log.push(`Initial value of ${propName} set to ${to}`);
      } else {
        let from = JSON.stringify(changedProp.previousValue);
        log.push(`${propName} changed from ${from} to ${to}`);
      }
    }
    this.changeLog.push(log.join(', '));
  }
複製程式碼

備註:若輸入型別是物件等引入型別資料。 Angular 不會關注這個引入型別的某個屬性的變化。 只要引用沒有發生變化,於是從 Angular 的視角看來,也就沒有什麼需要報告的變化了。

四、父元件監聽子元件的事件

  • 子元件暴露一個 EventEmitter 屬性,當事件發生時,子元件利用該屬性 emits(向上彈射)事件。父元件繫結到這個事件屬性,並在事件發生時作出回應。
  • 子元件的 EventEmitter 屬性是一個輸出屬性,帶有@Output 裝飾器。
// 子元件
export class VoterComponent {
  @Input()  name: string;
  @Output() voted = new EventEmitter<boolean>();
  didVote = false;
  vote(agreed: boolean) {
    this.voted.emit(agreed);
    this.didVote = true;
  }
}
複製程式碼
// 父元件
import { Component }      from '@angular/core';
@Component({
  selector: 'app-vote-taker',
  template: `
    <h2>Should mankind colonize the Universe?</h2>
    <h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
    <app-voter *ngFor="let voter of voters"
      [name]="voter"
      (voted)="onVoted($event)">
    </app-voter>
  `
})
export class VoteTakerComponent {
  agreed = 0;
  disagreed = 0;
  voters = ['Mr. IQ', 'Ms. Universe', 'Bombasto'];
  onVoted(agreed: boolean) {
    agreed ? this.agreed++ : this.disagreed++;
  }
}
複製程式碼

四、父元件讀取子元件的屬性或呼叫子元件方法

在父元件中新建一個本地變數來代表子元件,然後利用這個變數來讀取子元件的屬性和呼叫子元件的方法。

// 父元件
<button (click)="timer.start()">Start</button>
<button (click)="timer.stop()">Stop</button>
<app-countdown-timer #timer></app-countdown-timer>
複製程式碼
// 子元件
start() { this.countDown(); }
stop()  {
this.clearTimer();
this.message = `Holding at T-${this.seconds} seconds`;
}
複製程式碼

五、父元件呼叫@ViewChild()

如果 父元件的類 需要讀取子元件的屬性值或呼叫子元件的方法,就不能使用本地變數方法(本地變數只能在模板中進行)。當父元件類需要這種訪問時,可以把子元件作為 ViewChild,注入到父元件裡面。

// 父元件
import { AfterViewInit, ViewChild } from '@angular/core';
import { Component }                from '@angular/core';
import { CountdownTimerComponent }  from './countdown-timer.component';

@Component({
  selector: 'app-countdown-parent-vc',
  template: `
  <h3>Countdown to Liftoff (via ViewChild)</h3>
  <button (click)="start()">Start</button>
  <button (click)="stop()">Stop</button>
  <div class="seconds">{{ seconds() }}</div>
  <app-countdown-timer></app-countdown-timer>
  `,
  styleUrls: ['../assets/demo.css']
})
export class CountdownViewChildParentComponent implements AfterViewInit {

  @ViewChild(CountdownTimerComponent)
  private timerComponent: CountdownTimerComponent;

  seconds() { return 0; }

  ngAfterViewInit() {
    setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
  }

  start() { this.timerComponent.start(); }
  stop() { this.timerComponent.stop(); }
}
複製程式碼

六、父元件和子元件通過服務來通訊

終極絕招,父元件和它的子元件共享同一個服務,利用該服務在元件家族內部實現雙向通訊。該服務例項的作用域被限制在父元件和其子元件內。這個元件子樹之外的元件將無法訪問該服務或者與它們通訊。

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

@Injectable()
export class MissionService {

  // Observable string sources
  private missionAnnouncedSource = new Subject<string>();
  private missionConfirmedSource = new Subject<string>();

  // Observable string streams
  missionAnnounced$ = this.missionAnnouncedSource.asObservable();
  missionConfirmed$ = this.missionConfirmedSource.asObservable();

  // Service message commands
  announceMission(mission: string) {
    this.missionAnnouncedSource.next(mission);
  }

  confirmMission(astronaut: string) {
    this.missionConfirmedSource.next(astronaut);
  }
}
複製程式碼
// 子元件
import { Component, Input, OnDestroy } from '@angular/core';

import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs';
export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;

  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }

  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }

  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}
複製程式碼

相關文章