Angular - ngOnChanges- Angular LifeCycle Hook (2/2)

  • 591
  • 0
  • 2018-05-22

介紹 ngOnChanges 比較進階的用法

在之前學習Angular 的過程中,保哥都有強調

不要在子元件內直接異動父元件傳來的資料,

直接異動的話其他的子元件會無法知道這些異動。

 

因此,我們在實作的過程中,都會刻意在子元件內避免使用 [(ngModle)] 雙向繫結 來直接繫結 父元件的來源資料

直接繫結的話子元件修改資料就直接異動到父元件的資料,這不是一個好的設計模式

可以活用 ngOnChanges 來改善這個問題,

首先複習一下ngOnChanges 的觸發時機點:

當父類別透過透過Property Binding (屬性繫結)將資料傳到子元件,

子元件使用宣告 @Input() 的變數來接收父類別傳來的資料,

這時候子元件的ngOnChange就會被觸發,當父類別的來源資料有異動的時候,也會重新觸發 ngOnChanges()

 

範例如下

首先定義Component.ts

export class DemoChildComponent implements OnInit, OnChanges {
  isEdit = false;

  // 宣告一個Original Item 去保留父元件傳過來的物件原始資料
  // template在雙向繫結的時候不繫結這個物件,這樣就不會直接異動到父元件的來源資料  
  OriItem = {};  
  @Input() inItem;

  // 真正要異動父元件資料的時候,透過底下宣告的兩個 @Output() 去觸發父元件,由父元件內部實際去修改
  // 修改完成之後,父元件會觸發 ngOnChanges,資料就會重新整理
  @Output() ouEditContent = new EventEmitter<any>();
  @Output() ouDelItem = new EventEmitter<any>();

  constructor() {}

  ngOnInit() {}

  // changeItems 是一個物件,裡面可能有一個以上的屬性(每一個 @Input() 就會占用一個屬性)
  ngOnChanges(changeItems) {
    // inItem 這個屬性是 @Input() 變數名稱,用判斷屬性名稱的方式來設計之後要做的事情
    if (changeItems.inItem) {
      // currentValue 是真正使用的@Input() 資料內容,將這個值傳給 OriItem (保留父元件的原始資料)
      this.OriItem = changeItems.inItem.currentValue;
      
      // 透過產生 "新"物件的方式來重新產生 inItem 物件內容,畫面上雙向繫結的對象就是這個新的物件
      this.inItem = Object.assign({}, changeItems.inItem.currentValue);
    }
  }

  // 確認存檔(異動父元件內容)
  SaveEdit() {
    // 確定要存檔的時候,使用上雙向繫結對應物件的內容來異動父元件資料
    this.ouEditContent.emit(this.inItem);
  }

  // 取消修改
  CancelEdit() {
    // 將ngOnChanges 一開始保留在OriItem地資料內容,透過產生 "新"物件的方式來重新產生 inItem 物件內容
    this.inItem = Object.assign({}, this.OriItem);
    this.isEdit = false;
  }

  DeleteItem() {
    // 確認要刪除
    this.ouDelItem.emit(this.inItem);
  }
}

template:

這邊的重點是,[(ngModel)] 雙向繫結的對象,是在ts內透過Object.assign 產生的新物件 inItem,不會直接修改到父元件的資料

<div>
    <div *ngIf="!isEdit">
        <a [href]="inItem['href-link']">{{inItem.title}}</a>
        <br>
        <span>Date:{{inItem.date}}</span>
        <br>
        <span>{{inItem.content}}</span>
    </div>

    <div *ngIf="isEdit">
        <span>content:</span>
        <input type="text" [(ngModel)]="inItem.content" (keyup.enter)="SaveEdit()" (keyup.escape)="CancelEdit()">
    </div>

    <div>
        <button (click)="DeleteItem()">Delete</button>
        <button (click)="this.isEdit = true;">Edit</button>
    </div>
    <span>============================================================</span>
</div>

 

一旦父元件的資料內容被異動後,

就會再透過觸發 ngOnChanges 來重新更新子元件的內容。

 

結論:

1.

透過重新產生新物件的方式,讓 template 雙向繫結新的物件,

這樣既不會直接異動到父元件的物件,又可以使用好用的雙向繫結。

2.

使用 ngOnChanges 來更新子元件的資料內容,

ngOnChanges()的參數是一個物件,

裡面可能有一個以上的屬性(每一個 @Input() 就會占用一個屬性),

每一個屬性會對應一個物件,

物件內容為: currentValue 、 firstChange、previousValue