簡體   English   中英

如何檢測 Angular 中的 @Input() 值何時發生變化?

[英]How to detect when an @Input() value changes in Angular?

我有一個父組件 ( CategoryComponent )、一個子組件 ( videoListComponent ) 和一個 ApiService。

我的大部分工作都很好,即每個組件都可以訪問 json api 並通過 observables 獲取其相關數據。

目前視頻列表組件只獲取所有視頻,我想將其過濾為特定類別中的視頻,我通過@Input()將 categoryId 傳遞給孩子來實現這一點。

類別組件.html

<video-list *ngIf="category" [categoryId]="category.id"></video-list>

這有效,當父 CategoryComponent 類別發生變化時,categoryId 值通過@Input()傳遞,但我需要在 VideoListComponent 中檢測到這一點,並通過 APIService 重新請求視頻數組(使用新的 categoryId)。

在 AngularJS 中,我會對變量執行$watch 處理這個問題的最佳方法是什么?

實際上,當 angular2+ 的子組件中的輸入發生變化時,有兩種方法可以檢測和采取行動:

  1. 您可以使用舊答案中也提到的ngOnChanges() 生命周期方法
    @Input() categoryId: string;
        
    ngOnChanges(changes: SimpleChanges) {
        
        this.doSomething(changes.categoryId.currentValue);
        // You can also use categoryId.previousValue and 
        // categoryId.firstChange for comparing old and new values
        
    }
    

文檔鏈接: ngOnChanges、 SimpleChanges、 SimpleChange
演示示例:看看這個 plunker

  1. 或者,您還可以使用輸入屬性設置器,如下所示:
    private _categoryId: string;
    
    @Input() set categoryId(value: string) {
    
       this._categoryId = value;
       this.doSomething(this._categoryId);
    
    }
    
    get categoryId(): string {
    
        return this._categoryId;
    
    }

文檔鏈接:看這里

演示示例:看看這個 plunker

您應該使用哪種方法?

如果您的組件有多個輸入,那么,如果您使用 ngOnChanges(),您將在 ngOnChanges() 中一次獲得所有輸入的所有更改。 使用這種方法,您還可以比較已更改的輸入的當前值和以前的值,並采取相應的措施。

但是,如果您只想在特定的單個輸入更改時執行某些操作(並且您不關心其他輸入),那么使用輸入屬性設置器可能會更簡單。 但是,這種方法沒有提供一種內置方法來比較更改輸入的先前值和當前值(您可以使用 ngOnChanges 生命周期方法輕松完成)。

編輯 2017-07-25:在某些情況下,角度變化檢測可能仍然不會觸發

通常,只要父組件更改它傳遞給子組件的數據,setter 和 ngOnChanges 的更改檢測就會觸發,前提是數據是 JS 原始數據類型(string, number, boolean) 但是,在以下情況下,它不會觸發,您必須采取額外的措施才能使其工作。

  1. 如果您使用嵌套對象或數組(而不是 JS 原始數據類型)將數據從父級傳遞給子級,則更改檢測(使用 setter 或 ngchanges)可能不會觸發,正如用戶回答中提到的:muetzerich。 對於解決方案看這里

  2. 如果您在角度上下文之外(即外部)改變數據,那么角度將不知道這些變化。 您可能必須在組件中使用 ChangeDetectorRef 或 NgZone 以從角度感知外部變化,從而觸發變化檢測。 參考這個

在組件中使用ngOnChanges()生命周期方法。

ngOnChanges 在數據綁定屬性被檢查之后和視圖和內容子元素被檢查之前被調用,如果它們中的至少一個已經改變。

這是文檔

在函數簽名中使用SimpleChanges類型時,我在控制台以及編譯器和 IDE 中遇到錯誤。 為防止出現錯誤,請改用簽名中的any關鍵字。

ngOnChanges(changes: any) {
    console.log(changes.myInput.currentValue);
}

編輯:

正如 Jon 在下面指出的,您可以在使用括號表示法而不是點表示法時使用SimpleChanges簽名。

ngOnChanges(changes: SimpleChanges) {
    console.log(changes['myInput'].currentValue);
}
@Input() set categoryId(categoryId: number) {
      console.log(categoryId)
}

請嘗試使用此方法。 希望這可以幫助

最安全的選擇是使用共享服務而不是@Input參數。 此外, @Input參數不會檢測復雜嵌套對象類型的變化。

一個簡單的示例服務如下:

服務.ts

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

@Injectable()
export class SyncService {

    private thread_id = new Subject<number>();
    thread_id$ = this.thread_id.asObservable();

    set_thread_id(thread_id: number) {
        this.thread_id.next(thread_id);
    }

}

組件.ts

export class ConsumerComponent implements OnInit {

  constructor(
    public sync: SyncService
  ) {
     this.sync.thread_id$.subscribe(thread_id => {
          **Process Value Updates Here**
      }
    }
  
  selectChat(thread_id: number) {  <--- How to update values
    this.sync.set_thread_id(thread_id);
  }
}

您可以在其他組件中使用類似的實現,並且您的所有組件都將共享相同的共享值。

角 ngOnChanges

ngOnChanges()是一種內置的 Angular 回調方法,在默認更改檢測器檢查數據綁定屬性(如果至少有一個已更改)后立即調用該方法。 在查看和內容之前,檢查孩子。

// child.component.ts

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

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit, OnChanges {

  @Input() inputParentData: any;

  constructor() { }

  ngOnInit(): void {
  }

  ngOnChanges(changes: SimpleChanges): void {
    console.log(changes);
  }

}

更多信息: Angular 文檔

我只想補充一點,如果@Input值不是原始值,還有另一個名為DoCheck的生命周期鈎子很有用。

我有一個數組作為Input ,因此當內容更改時它不會觸發OnChanges事件(因為 Angular 所做的檢查是“簡單”且不深入,因此數組仍然是一個數組,即使數組上的內容已更改)。

然后我實現了一些自定義檢查代碼來決定是否要使用更改后的數組來更新我的視圖。

當您的輸入屬性更改時,這里 ngOnChanges 將始終觸發:

ngOnChanges(changes: SimpleChanges): void {
 console.log(changes.categoryId.currentValue)
}

您還可以擁有一個觸發父component(CategoryComponent)更改的可觀察對象,並在子組件的訂閱中執行您想要執行的操作。 videoListComponent

服務.ts

public categoryChange$ : ReplaySubject<any> = new ReplaySubject(1);

類別組件.ts

public onCategoryChange(): void {
  service.categoryChange$.next();
}

視頻列表組件.ts

public ngOnInit(): void {
  service.categoryChange$.subscribe(() => {
   // do your logic
  });
}

我會堅持采用@alan-cs 建議的方法,但要進行一些修改。 首先 - 我反對使用ngOnChanges 相反,我建議將所有需要更改的內容移到一個對象下。 並使用BehaviorSubject跟蹤它的變化:

  private location$: BehaviorSubject<AbxMapLayers.Location> = new BehaviorSubject<AbxMapLayers.Location>(null);

  @Input()
  set location(value: AbxMapLayers.Location) {
    this.location$.next(value);
  }
  get location(): AbxMapLayers.Location {
    return this.location$.value;
  }

<abx-map-layer
    *ngIf="isInteger(unitForm.get('addressId').value)"
    [location]="{
      gpsLatitude: unitForm.get('address.gpsLatitude').value,
      gpsLongitude: unitForm.get('address.gpsLongitude').value,
      country: unitForm.get('address.country').value,
      placeName: unitForm.get('address.placeName').value,
      postalZip: unitForm.get('address.postalZip').value,
      streetName: unitForm.get('address.streetName').value,
      houseNumber: unitForm.get('address.houseNumber').value
    }"
    [inactive]="unitAddressForm.disabled"
    >
</abx-map-layer>

你也可以只傳遞一個 EventEmitter 作為輸入。 不太確定這是否是最佳實踐...

類別組件.ts:

categoryIdEvent: EventEmitter<string> = new EventEmitter<>();

- OTHER CODE -

setCategoryId(id) {
 this.category.id = id;
 this.categoryIdEvent.emit(this.category.id);
}

類別組件.html:

<video-list *ngIf="category" [categoryId]="categoryIdEvent"></video-list>

在 VideoListComponent.ts 中:

@Input() categoryIdEvent: EventEmitter<string>
....
ngOnInit() {
 this.categoryIdEvent.subscribe(newID => {
  this.categoryId = newID;
 }
}

此解決方案使用代理類並提供以下優勢:

  • 允許消費者利用RXJS的力量
  • 比目前提出的其他解決方案更緊湊
  • 比使用ngOnChanges ngOnChanges()更安全

示例用法:

@Input()
public num: number;
numChanges$ = observeProperty(this as MyComponent, 'num');

實用功能:

export function observeProperty<T, K extends keyof T>(target: T, key: K) {
  const subject = new BehaviorSubject<T[K]>(target[key]);
  Object.defineProperty(target, key, {
    get(): T[K] { return subject.getValue(); },
    set(newValue: T[K]): void {
      if (newValue !== subject.getValue()) {
        subject.next(newValue);
      }
    }
  });
  return subject;
}

您可以在外觀服務中使用BehaviorSubject ,然后在任何組件中訂閱該主題,當事件發生時觸發數據調用.next()的更改。 確保在 on destroy 生命周期鈎子中關閉這些訂閱。

數據-api.facade.ts

@Injectable({
  providedIn: 'root'
})
export class DataApiFacade {

currentTabIndex: BehaviorSubject<number> = new BehaviorSubject(0);

}

一些.component.ts

constructor(private dataApiFacade: DataApiFacade){}

ngOnInit(): void {
  this.dataApiFacade.currentTabIndex
    .pipe(takeUntil(this.destroy$))
       .subscribe(value => {
          if (value) {
             this.currentTabIndex = value;
          }
    });
}

setTabView(event: MatTabChangeEvent) {
  this.dataApiFacade.currentTabIndex.next(event.index);
}

ngOnDestroy() {
  this.destroy$.next(true);
  this.destroy$.complete();
}

您可以使用ngOnChanges() 生命周期方法

@Input() inputValue: string;

ngOnChanges(changes: SimpleChanges) {
    console.log(changes['inputValue'].currentValue);
}

基本上,兩種建議的解決方案在大多數情況下都可以正常工作。 我對ngOnChange()的主要負面體驗是缺乏類型安全性。

在我的一個項目中,我做了一些重命名,之后一些魔術字符串保持不變,當然這個錯誤需要一些時間才能浮出水面。

Setter 沒有這個問題:您的 IDE 或編譯器會讓您知道任何不匹配的情況。

如果您不想使用ngOnChange實現 og onChange()方法,您也可以通過valueChanges事件等訂閱特定項目的更改。

myForm = new FormGroup({
  first: new FormControl(),
});

this.myForm.valueChanges.subscribe((formValue) => {
  this.changeDetector.markForCheck();
});

由於在此聲明中使用而編寫的markForCheck()

changeDetection: ChangeDetectionStrategy.OnPush

如果您正在處理使用@Input在父組件和子組件之間共享數據的情況,您可以使用生命周期方法檢測@Input數據更改: ngOnChanges

 ngOnChanges(changes: SimpleChanges) {
    if (!changes.categoryId.firstChange) {
      // only logged upon a change after rendering
      console.log(changes.categoryId.currentValue);
    }
  }

而且我建議您應該注意為子組件實施的更改策略,出於某種性能原因,您應該添加 ChangeDetectionStrategy.OnPush :

示例代碼:

@Component({
  selector: 'app-hero-detail',
  templateUrl: './hero-detail.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class VideoListComponent implements OnChanges {
  @Input() categoryId: string;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM