1. 程式人生 > >Angular元件通訊

Angular元件通訊

通過輸入型繫結把資料從父元件傳到子元件; 通過 setter 截聽輸入屬性值的變化;通過ngOnChanges()來截聽輸入屬性值的變化;父元件監聽子元件的事件; 父元件與子元件通過本地變數互動;父元件呼叫@ViewChild();父元件和子元件通過服務來通訊

  1. 元件通過輸入型繫結把資料從父元件傳到子元件,主要是利用元件的輸入型屬性,帶@Input裝飾器,通過輸入屬性可以將父元件的資料傳入子元件。具體例子如下:

    子元件程式碼為:

    import { Component, OnInit, Input } from '@angular/core';
    
    @Component({
     selector: 'app-home'
    , templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit { @Input() isShow: boolean; @Input() noShow: boolean; user = 'wallowyou'; constructor() { } ngOnInit() { } } // 子元件模板 <p> 您好,{{ user }}這是主頁,歡迎來到主頁! </p> <div
    *ngIf="isShow"> 可以看到 </div> <div *ngIf="noShow"> 不可以看到 </div>

    其中 @Input() isShow,@Input() noShow是子元件的兩個輸入屬性,也可以說是接受資料的屬性吧。

    父元件程式碼為:

    import { Component } from '@angular/core';
    
    @Component({
     selector: 'app-root',
     templateUrl: './app.component.html',
     styleUrls: ['./app.component.css'
    ] }) export class AppComponent { title = 'app'; canSee = true; noSee = true; } // 父元件模板為 <div class="main"> <app-home [isShow]="canSee" [noShow]="noSee"></app-home> </div>

    這樣相當於把父元件中兩個屬性canSee和noSee的值傳遞給了子元件home,分別改動父元件的兩個屬性值觀察頁面是否變化可以看出是否把父元件的值正確的傳遞給了子元件。

    1. 通過 setter 截聽輸入屬性值的變化,使用一個輸入屬性的 setter,以攔截父元件中值的變化,並採取行動,將屬性變成了setter。

      子元件程式碼:

      import { Component, OnInit, Input } from '@angular/core';
      
      @Component({
      selector: 'app-home',
      templateUrl: './home.component.html',
      styleUrls: ['./home.component.css']
      })
      export class HomeComponent implements OnInit {
      private _isShow_: boolean;
      @Input()
       set isShow(isShow: boolean) {
         this._isShow_ = isShow;
       }
       get isShow(): boolean {
         return this._isShow_;
       }
      user = 'wallowyou';
      
      constructor() { }
      
      ngOnInit() {
      }
      
      }
      // 模板為
      <p>
      您好,{{ user }}這是主頁,歡迎來到主頁!
      </p>
      <div *ngIf="isShow">
      可以看到
      </div>

      父元件的程式碼為:

      import { Component } from '@angular/core';
      
      @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
      })
      export class AppComponent {
      title = 'app';
      canSee = true;
      }
      // 模板
      <div class="main">
        <app-home [isShow]="canSee"></app-home>    
      </div>
    2. 父元件通過監聽子元件的事件可以實現將子元件的資料傳給父元件,根據angular官方文件裡面一個投票的例子,具體程式碼為:

      子元件程式碼為:

      import { Component, OnInit, EventEmitter, Input, Output } from '@angular/core';
      
      @Component({
      selector: 'app-voter-child',
      templateUrl: './voter-child.component.html',
      styleUrls: ['./voter-child.component.css']
      })
      export class VoterChildComponent implements OnInit {
      @Input() name: string;
      @Output() toVoted = new EventEmitter<boolean>();
      voted = false;
      constructor() { }
      
      ngOnInit() {
      }
      vote(agreed: boolean) {
        this.voted = true;
        this.toVoted.emit(agreed);
      }
      
      }
      // 模板
      <div class="voter-list" *ngIf="name">
      <div>
        <label>投票人</label> {{ name }}
      </div>
      <div>
        <button (click)="vote(true)" [disabled]="voted">贊成</button>
        <button (click)="vote(false)" [disabled]="voted">反對</button>
      </div>
      </div>
      

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

      子元件的 EventEmitter 屬性是一個輸出屬性,通常帶有@Output 裝飾器

      父元件程式碼為:

      import { Component, OnInit } from '@angular/core';
      
      @Component({
      selector: 'app-voters-display',
      templateUrl: './voters-display.component.html',
      styleUrls: ['./voters-display.component.css']
      })
      export class VotersDisplayComponent implements OnInit {
      voterList = [
        {
          id: 1, name: '小明'
        },
        {
          id: 2, name: '小芳'
        },
        {
          id: 3, name: '小紅'
        }
      ];
      agreeCount = 0;
      disagreeCount = 0;
      constructor() { }
      
      ngOnInit() {
      }
      toVoted(agreed: boolean) {
        agreed ? this.agreeCount++ : this.disagreeCount++;
      }
      
      }
      // 模板
      <div>
      投票結果為:
      <label>贊成</label> <span>{{ agreeCount }}票</span>
      <label>反對</label> <span>{{ disagreeCount }}票</span>
      </div>
      <app-voter-child *ngFor="let item of voterList" [name]="item.name" (toVoted)="toVoted($event)"></app-voter-child>

      父元件 綁定了一個事件處理器(onVoted()),用來響應子元件的事件($event)並更新一個計數器。

      1. 父元件與子元件通過本地變數互動

        父元件不能使用資料繫結來讀取子元件的屬性或呼叫子元件的方法。但可以在父元件模板裡,新建一個本地變數來代表子元件,然後利用這個變數來讀取子元件的屬性和呼叫子元件的方法,以下以官方的demo為例。

        子元件程式碼:

        import { Component, OnInit, OnDestroy } from '@angular/core';
        
        @Component({
        selector: 'app-couter-child',
        templateUrl: './couter-child.component.html',
        styleUrls: ['./couter-child.component.css']
        })
        export class CouterChildComponent implements OnInit, OnDestroy {
        message = '';
        intervalId = 0;
        seconds = 11;
        clearTimer() { clearInterval(this.intervalId); }
        constructor() { }
        
        ngOnInit() {
        }
        ngOnDestroy() {
        }
        start() { this.countDown(); }
        stop()  {
         this.clearTimer();
         this.message = `停止於${this.seconds} seconds`;
        }
        
        private countDown() {
         this.clearTimer();
         this.intervalId = window.setInterval(() => {
           this.seconds -= 1;
           if (this.seconds === 0) {
             this.message = '倒計時完畢!';
           } else {
             if (this.seconds < 0) { this.seconds = 10; } // reset
             this.message = `T-${this.seconds} seconds and counting`;
           }
         }, 1000);
        }
        
        }
        // 模板
        <p>
        {{ message }}
        </p>

        父元件程式碼:

        import { Component, OnInit } from '@angular/core';
        
        @Component({
        selector: 'app-couter-parent',
        templateUrl: './couter-parent.component.html',
        styleUrls: ['./couter-parent.component.css']
        })
        export class CouterParentComponent implements OnInit {
        
        constructor() { }
        
        ngOnInit() {
        }
        
        }
        // 模板
        <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-couter-child #timer></app-couter-child>

        父元件不能通過資料繫結使用子元件的 startstop 方法,也不能訪問子元件的 seconds 屬性。

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

        1. 父元件呼叫@ViewChild()

        本地變數方法是個簡單便利的方法。但是它也有侷限性,因為父元件-子元件的連線必須全部在父元件的模板中進行。父元件本身的程式碼對子元件沒有訪問權。

        如果父元件的需要讀取子元件的屬性值或呼叫子元件的方法,就不能使用本地變數方法。

        當父元件需要這種訪問時,可以把子元件作為 ViewChild注入到父元件裡面。

        子元件程式碼跟4一樣,主要是改變了父元件的程式碼

        import { Component, OnInit, AfterViewInit, ViewChild } from '@angular/core';
        import { CouterChildComponent } from '../couter-child/couter-child.component';
        
        @Component({
          selector: 'app-couter-view-child',
          templateUrl: './couter-view-child.component.html',
          styleUrls: ['./couter-view-child.component.css']
        })
        export class CouterViewChildComponent implements OnInit, AfterViewInit {
          @ViewChild(CouterChildComponent)
          private timerComponent: CouterChildComponent;
        
          seconds() { return 0; }
        
          ngAfterViewInit() {
            setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
          }
        
          start() { this.timerComponent.start(); }
          stop() { this.timerComponent.stop(); }
          constructor() { }
        
          ngOnInit() {
          }
        
        }
        // 模板
        <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>

        首先,你要使用 ViewChild 裝飾器匯入這個引用,並掛上 AfterViewInit 生命週期鉤子。

        接著,通過 @ViewChild 屬性裝飾器,將子元件 CouterChildComponent注入到私有屬性 timerComponent 裡面。

        元件元資料裡就不再需要 #timer 本地變量了。而是把按鈕繫結到父元件自己的 startstop 方法,使用父元件的 seconds 方法的插值表示式來展示秒數變化。

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

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

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

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