Angular8--父子組件之間的傳值

目錄

一、通過輸入型綁定把數據從父組件傳到子組件。

二、父組件監聽子組件的事件

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

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

五、父組件調用@ViewChild()


https://angular.cn/guide/component-interaction官方文檔

https://blog.csdn.net/u012967849/article/details/78767294

在該實例中牽扯到的父子文件。

父組件:parent.component.html;    parent.component.ts

子組件:child.component.html;    child.component.ts

一、通過輸入型綁定把數據從父組件傳到子組件。

@Input :  

父組件給子組件屬性值;父組件用子組件的屬性值和方法建議用@ViewChild,若用@Input可能需要做數據攔截。
將父作用域中的值“輸入”到子作用域中,之後子作用域進行相關處理
@Output :
子作用域觸發事件執行響應函數,而響應函數方法體則位於父作用域中,相當於將事件“輸出”到父作用域中,在父作用域中處理。
Output一般都是一個EventEmitter的實例,使用實例的emit方法將參數emit到父組件中,觸發父組件中對應的事件。

parent.component.html文件中:
<app-child [groupIdName]="serviceSelected?.group_id" (addSuccess)="addSuccessEvent()"></app-child>
child.component.ts文件中:
export class AddServiceComponent {
    // 子組件暴露一個 EventEmitter 屬性
  @Output() addSuccess = new EventEmitter();
    // @Input爲子組件的屬性名groupId指定一個別名groupIdName
  @Input('groupIdName') groupId: number;
  constructor(private http: HttpClient) {}
  handleOk(){
     this.http.post(url, params).subscribe(
     () => {
        this.message.success(this.serviceAddData.id ? '編輯成功' : '添加成功');
        this.addSuccess.emit();
        this.handleCancel();
        this.addForm.form.reset();
      }
    )}
    // 在子組件中使用父組件傳遞過來的值
    open(data = {}) {
    this.serviceAddData = Object.assign({
      name: '',
      ability_ids: [],
      group_id: this.groupId
    }, data);
  }
}

二、父組件監聽子組件的事件

子組件暴露一個 EventEmitter 屬性,當事件發生時,子組件利用該屬性 emits(向上彈射)事件。父組件綁定到這個事件屬性,並在事件發生時作出迴應。

子組件的 EventEmitter 屬性是一個輸出屬性,通常帶有@Output 裝飾器,就像在 VoterComponent 中看到的。

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

使用一個輸入屬性的 setter,以攔截父組件中值的變化,並採取行動。

子組件 NameChildComponent 的輸入屬性 name 上的這個 setter,會 trim 掉名字裏的空格,並把空值替換成默認字符串。

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) {
    this._name = (name && name.trim()) || '<no name set>';
  }

  get name(): string { return this._name; }
}

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

使用 OnChanges 生命週期鉤子接口的 ngOnChanges() 方法來監測輸入屬性值的變化並做出迴應。

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

@Component({
  selector: 'app-version-child',
  template: `
    <h3>Version {{major}}.{{minor}}</h3>
    <h4>Change log:</h4>
    <ul>
      <li *ngFor="let change of changeLog">{{change}}</li>
    </ul>
  `
})
export class VersionChildComponent implements OnChanges {
  @Input() major: number;
  @Input() minor: number;
  changeLog: string[] = [];

  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(', '));
  }
}

五、父組件調用@ViewChild()

這個本地變量方法是個簡單便利的方法。但是它也有侷限性,因爲父組件-子組件的連接必須全部在父組件的模板中進行。父組件本身的代碼對子組件沒有訪問權。

如果父組件的需要讀取子組件的屬性值或調用子組件的方法,就不能使用本地變量方法。

當父組件需要這種訪問時,可以把子組件作爲 ViewChild注入到父組件裏面。

下面的例子用與倒計時相同的範例來解釋這種技術。 它的外觀或行爲沒有變化。子組件CountdownTimerComponent也和原來一樣。

本地變量切換到 ViewChild 技術的唯一目的就是做示範。

下面是父組件 CountdownViewChildParentComponent:

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() {
    // Redefine `seconds()` to get from the `CountdownTimerComponent.seconds` ...
    // but wait a tick first to avoid one-time devMode
    // unidirectional-data-flow-violation error
    setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
  }

  start() { this.timerComponent.start(); }
  stop() { this.timerComponent.stop(); }
}

把子組件的視圖插入到父組件類需要做一點額外的工作。

首先,你必須導入對裝飾器 ViewChild 以及生命週期鉤子 AfterViewInit 的引用。

接着,通過 @ViewChild 屬性裝飾器,將子組件 CountdownTimerComponent 注入到私有屬性 timerComponent 裏面。

組件元數據裏就不再需要 #timer 本地變量了。而是把按鈕綁定到父組件自己的 start 和 stop 方法,使用父組件的 seconds 方法的插值來展示秒數變化。

這些方法可以直接訪問被注入的計時器組件。

ngAfterViewInit() 生命週期鉤子是非常重要的一步。被注入的計時器組件只有在 Angular 顯示了父組件視圖之後才能訪問,所以它先把秒數顯示爲 0.

然後 Angular 會調用 ngAfterViewInit 生命週期鉤子,但這時候再更新父組件視圖的倒計時就已經太晚了。Angular 的單向數據流規則會阻止在同一個週期內更新父組件視圖。應用在顯示秒數之前會被迫再等一輪

使用 setTimeout() 來等下一輪,然後改寫 seconds() 方法,這樣它接下來就會從注入的這個計時器組件裏獲取秒數的值。 

六、父組件與子組件通過本地變量互動

https://angular.cn/guide/component-interaction#parent-calls-an-viewchild

父組件不能使用數據綁定來讀取子組件的屬性或調用子組件的方法。但可以在父組件模板裏,新建一個本地變量來代表子組件,然後利用這個變量來讀取子組件的屬性和調用子組件的方法,如下例所示。

子組件 CountdownTimerComponent 進行倒計時,歸零時發射一個導彈。start 和 stop 方法負責控制時鐘並在模板裏顯示倒計時的狀態信息。

子組件:

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

@Component({
  selector: 'app-countdown-timer',
  template: '<p>{{message}}</p>'
})
export class CountdownTimerComponent implements OnInit, OnDestroy {

  intervalId = 0;
  message = '';
  seconds = 11;

  clearTimer() { clearInterval(this.intervalId); }

  ngOnInit()    { this.start(); }
  ngOnDestroy() { this.clearTimer(); }

  start() { this.countDown(); }
  stop()  {
    this.clearTimer();
    this.message = `Holding at T-${this.seconds} seconds`;
  }

  private countDown() {
    this.clearTimer();
    this.intervalId = window.setInterval(() => {
      this.seconds -= 1;
      if (this.seconds === 0) {
        this.message = 'Blast off!';
      } else {
        if (this.seconds < 0) { this.seconds = 10; } // reset
        this.message = `T-${this.seconds} seconds and counting`;
      }
    }, 1000);
  }
}

父組件:

import { Component }                from '@angular/core';
import { CountdownTimerComponent }  from './countdown-timer.component';

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

父組件不能通過數據綁定使用子組件的 start 和 stop 方法,也不能訪問子組件的 seconds 屬性。

把本地變量(#timer)放到(<countdown-timer>)標籤中,用來代表子組件。這樣父組件的模板就得到了子組件的引用,於是可以在父組件的模板中訪問子組件的所有屬性和方法。

這個例子把父組件的按鈕綁定到子組件的 start 和 stop 方法,並用插值來顯示子組件的 seconds 屬性。

七、父組件和子組件通過服務來通訊

父組件和它的子組件共享同一個服務,利用該服務在組件家族內部實現雙向通訊。

該服務實例的作用域被限制在父組件和其子組件內。這個組件子樹之外的組件將無法訪問該服務或者與它們通訊。

這個 MissionService 把 MissionControlComponent 和多個 AstronautComponent 子組件連接起來。

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);
  }
}

MissionControlComponent 提供服務的實例,並將其共享給它的子組件(通過 providers 元數據數組),子組件可以通過構造函數將該實例注入到自身。

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

import { MissionService }     from './mission.service';

@Component({
  selector: 'app-mission-control',
  template: `
    <h2>Mission Control</h2>
    <button (click)="announce()">Announce mission</button>
    <app-astronaut *ngFor="let astronaut of astronauts"
      [astronaut]="astronaut">
    </app-astronaut>
    <h3>History</h3>
    <ul>
      <li *ngFor="let event of history">{{event}}</li>
    </ul>
  `,
  providers: [MissionService]
})
export class MissionControlComponent {
  astronauts = ['Lovell', 'Swigert', 'Haise'];
  history: string[] = [];
  missions = ['Fly to the moon!',
              'Fly to mars!',
              'Fly to Vegas!'];
  nextMission = 0;

  constructor(private missionService: MissionService) {
    missionService.missionConfirmed$.subscribe(
      astronaut => {
        this.history.push(`${astronaut} confirmed the mission`);
      });
  }

  announce() {
    let mission = this.missions[this.nextMission++];
    this.missionService.announceMission(mission);
    this.history.push(`Mission "${mission}" announced`);
    if (this.nextMission >= this.missions.length) { this.nextMission = 0; }
  }
}

AstronautComponent 也通過自己的構造函數注入該服務。由於每個 AstronautComponent 都是 MissionControlComponent 的子組件,所以它們獲取到的也是父組件的這個服務實例。

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

import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs';

@Component({
  selector: 'app-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})
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();
  }
}

注意,這個例子保存了 subscription 變量,並在 AstronautComponent 被銷燬時調用 unsubscribe() 退訂。 這是一個用於防止內存泄漏的保護措施。實際上,在這個應用程序中並沒有這個風險,因爲 AstronautComponent 的生命期和應用程序的生命期一樣長。但在更復雜的應用程序環境中就不一定了。

不需要在 MissionControlComponent 中添加這個保護措施,因爲它作爲父組件,控制着 MissionService 的生命期。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章