@Input()値がAngularで変更されたことを検出する方法は?


471

親コンポーネント(CategoryComponent)、子コンポーネント(videoListComponent)、およびApiServiceがあります。

私はこれのほとんどがうまく機能しています。つまり、各コンポーネントはjson apiにアクセスして、オブザーバブルを介して関連データを取得できます。

現在、動画リストコンポーネントはすべての動画を取得するだけです。これを特定のカテゴリの動画のみにフィルタリングしたいと思います@Input()

CategoryComponent.html

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

これは機能し、CategoryComponentの親カテゴリが変更されると、categoryId値が経由で渡されますが@Input()、VideoListComponentでこれを検出し、APIService(新しいcategoryIdを使用)を介してビデオ配列を再リクエストする必要があります。

AngularJS $watchでは、変数に対してaを実行したでしょう。これを処理する最良の方法は何ですか?




:配列の変更のためのstackoverflow.com/questions/42962394/...
dasfdsa

回答:


720

実際には、子コンポーネントの入力がangular2 +で変化したときに検出して対処する方法が2つあります。

  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の
例:このプランカーを見てください。

  1. または、次のように入力プロパティセッターを使用することもできます。
    private _categoryId: string;

    @Input() set categoryId(value: string) {

       this._categoryId = value;
       this.doSomething(this._categoryId);

    }

    get categoryId(): string {

        return this._categoryId;

    }

ドキュメントリンク:こちらをご覧ください

デモ例:このプランカーを見てください。

どのアプローチを使用する必要がありますか?

コンポーネントに複数の入力がある場合、ngOnChanges()を使用すると、ngOnChanges()内ですべての入力のすべての変更を一度に取得できます。このアプローチを使用して、変更された入力の現在の値と以前の値を比較し、それに応じてアクションを実行することもできます。

ただし、特定の単一の入力のみが変更されたときに何かを実行したい場合(および他の入力を気にしない場合)、入力プロパティセッターを使用する方が簡単な場合があります。ただし、このアプローチは、変更された入力の以前の値と現在の値を比較する組み込みの方法を提供しません(これは、ngOnChangesライフサイクルメソッドで簡単に実行できます)。

2017-07-25を編集:いくつかの状況下では、角度変化の検出がうまくいかない場合があります

通常、データがJSプリミティブデータ型(string、number、boolean)である限り、setterとngOnChangesの両方の変更検出は、親コンポーネントが子に渡すデータを変更するたびに発生します。ただし、次のシナリオでは、起動せず、機能させるために追加のアクションを実行する必要があります。

  1. ネストされたオブジェクトまたは配列(JSプリミティブデータ型の代わりに)を使用してデータを親から子に渡す場合、ユーザーによる回答(muetzerich)でも説明されているように、変更検出(setterまたはngchangesのいずれかを使用)が起動しない場合があります。解決策については、こちらをご覧ください

  2. Angularのコンテキスト外で(つまり、外部で)データを変更する場合、Angleは変更を認識しません。コンポーネントでChangeDetectorRefまたはNgZoneを使用して、外部の変更を角度で認識し、変更の検出をトリガーする必要がある場合があります。こちらをご覧ください。


8
入力でセッターを使用することに関して非常に良い点です。より包括的であるため、これを受け入れられた回答に更新します。ありがとう。
Jon Catmull 2017年

2
@trichetriche、セッター(setメソッド)は、親が入力を変更するたびに呼び出されます。また、ngOnChanges()についても同様です。
アランCS

どうやらそうではない...私はこれを理解しようとするでしょう、それはおそらく私がした何か間違ったことです。

1
私はこの質問に遭遇しました、セッターを使用すると値を比較することはできないと述べましたが、それは真実ではありません。doSomething実際に新しい値を設定する前に、メソッドを呼び出して新しい値と古い値の2つの引数を取ることができます。設定する前に古い値を保存し、その後にメソッドを呼び出します。
T.Aoukar

1
@ T.Aoukar私が言っていることは、入力セッターはngOnChanges()のように古い値と新しい値の比較をネイティブでサポートしていないということです。いつでもハックを使用して古い値を格納し(言及)、新しい値と比較できます。
アランCS

105

ngOnChanges()コンポーネントでライフサイクルメソッドを使用します。

ngOnChangesは、データにバインドされたプロパティがチェックされた直後で、ビューとコンテンツの子の少なくとも1つが変更されているかどうかがチェックされる前に呼び出されます。

こちらがドキュメントです。


6
これは、変数が新しい値に設定されたMyComponent.myArray = []場合などに機能しますが、入力値が変更された場合などMyComponent.myArray.push(newValue)ngOnChanges()はトリガーされません。この変化を捉える方法について何かアイデアはありますか?
Levi Fuller

4
ngOnChanges()ネストされたオブジェクトが変更されたときは呼び出されません。多分あなたはここで
muetzerich

33

SimpleChanges関数シグネチャで型を使用すると、コンソールとコンパイラおよびIDEでエラーが発生しました。エラーを防ぐには、any代わりに署名でキーワードを使用します。

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

編集:

Jonが下で指摘したように、SimpleChangesドット表記ではなくブラケット表記を使用する場合は、署名を使用できます。

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

1
SimpleChangesファイルの上部にあるインターフェースをインポートしましたか?
Jon Catmull

@ジョン-はい。問題は署名自体ではなく、実行時にSimpleChangesオブジェクトに割り当てられたパラメーターにアクセスすることでした。たとえば、私のコンポーネントでは、Userクラスを入力(つまり<my-user-details [user]="selectedUser"></my-user-details>)にバインドしました。このプロパティは、SimpleChangesクラスからを呼び出してアクセスしますchanges.user.currentValue。通知userは実行時にバインドされ、SimpleChangesオブジェクトの一部ではありません
Darcy

1
これを試しましたか? ngOnChanges(changes: {[propName: string]: SimpleChange}) { console.log('onChanges - myProp = ' + changes['myProp'].currentValue); }
Jon Catmull

@ジョン-ブラケット表記に切り替えるとうまくいきます。私はそれを代替として含めるように私の回答を更新しました。
Darcy

1
'@ angular / core'から{SimpleChanges}をインポートします。それが角度のドキュメントにあり、ネイティブのtypescriptタイプではない場合、それはおそらく角度モジュールからのものであり、おそらく「angular / core」
BentOnCoding

13

最も安全な方法は、パラメータではなく共有サービスを使用する@Inputことです。また、@Inputパラメーターは、ネストされた複雑なオブジェクトタイプの変更を検出しません。

簡単なサービスの例は次のとおりです。

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

}

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

他のコンポーネントでも同様の実装を使用でき、すべてのコンポーネントが同じ共有値を共有します。


1
ありがとうサンケット。これは非常に良いポイントであり、適切な場合にはそれを行うためのより良い方法です。@Inputの変更の検出に関する私の特定の質問に答えるので、受け入れられた答えはそのままにしておきます。
Jon Catmull 2017

1
@Inputパラメーターは、複雑なネストされたオブジェクトタイプの変更検出しませんが、オブジェクト自体が変更された場合、それは発生しますよね?たとえば、入力パラメーター "parent"があるので、全体として親を変更すると変更検出が起動しますが、parent.nameを変更すると変更されませんか?
ヨギビンビ

ソリューションがより安全である理由を提供する必要があります
Joshua Kemmerer

1
@JoshuaKemmerer @Inputパラメーターは、複雑なネストされたオブジェクトタイプの変更を検出しませんが、単純なネイティブオブジェクトでは検出します。
Sanket Berde

6

値がプリミティブ値でないDoCheck場合に役立つ別のライフサイクルフックが存在することを追加したいだけです@Input

私は配列を持っているInputのでOnChanges、コンテンツが変更されてもイベントは発生しません(Angularのチェックが「単純」であり、深くないため、配列のコンテンツが変更されても、配列はまだ配列であるためです)。

次に、カスタムチェックコードを実装して、変更した配列でビューを更新するかどうかを決定します。


6
  @Input()
  public set categoryId(categoryId: number) {
      console.log(categoryId)
  }

この方法をお試しください。お役に立てれば


4

また、親コンポーネント(CategoryComponent)の変更をトリガーするオブザーバブルを用意し、子コンポーネントのサブスクリプションで実行したいことを行うこともできます。(videoListComponent)

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

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

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

2

ここで、ngOnChangesは、入力プロパティが変更されたときに常にトリガーされます。

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

1

このソリューションはプロキシクラスを使用し、次の利点を提供します。

  • 消費者がRXJSの機能を活用できるようにします
  • これまでに提案された他のソリューションよりコンパクト
  • 使用するよりタイプセーフ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;
}

0

ngOnChange implement og onChange()メソッドを使用しない場合は、valueChangesイベント、ETC によって特定のアイテムの変更をサブスクライブすることもできます。

 myForm= new FormGroup({
first: new FormControl()   

});

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

この宣言で使用するために作成されたmarkForCheck():

  changeDetection: ChangeDetectionStrategy.OnPush

3
これは質問とは完全に異なりますが、役立つ人はあなたのコードが別のタイプの変更に関するものであることを認識する必要があります。コンポーネントの外部からのデータ変更について質問し、コンポーネントにデータが変更されたという事実を認識して対応させる。あなたの答えは、コンポーネントへのローカルなフォームの入力のようなものについて、コンポーネント自体内の変更を検出することです。
rmcsharry

0

また、EventEmitterを入力として渡すこともできます。これがベストプラクティスであるかどうかはよくわかりません...

CategoryComponent.ts:

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

- OTHER CODE -

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

CategoryComponent.html:

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

そして、VideoListComponent.tsで:

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

ソリューションをサポートするための有用なリンク、コードスニペットを提供してください。
mishsx

例を追加しました。
セドリックシュヴァイツァー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.