Wie erkenne ich, wenn sich ein @ Input () -Wert in Angular ändert?

470

Ich habe eine übergeordnete Komponente ( CategoryComponent ), eine untergeordnete Komponente ( videoListComponent ) und einen ApiService.

Ich habe das meiste davon in Ordnung, dh jede Komponente kann auf die JSON-API zugreifen und ihre relevanten Daten über Observables abrufen.

Derzeit erhält die Videolistenkomponente nur alle Videos. Ich möchte dies nur nach Videos in einer bestimmten Kategorie filtern. Dies habe ich erreicht, indem ich die categoryId über an das Kind übergeben habe @Input().

CategoryComponent.html

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

Dies funktioniert und wenn sich die übergeordnete CategoryComponent-Kategorie ändert, wird der categoryId-Wert über weitergeleitet, @Input()aber ich muss dies in VideoListComponent erkennen und das Video-Array über APIService (mit der neuen categoryId) erneut anfordern.

In AngularJS hätte ich eine $watchVariable erstellt. Was ist der beste Weg, um damit umzugehen?

Jon Catmull
quelle
Änderungen der Eingabe beobachten: - angulartutorial.net/2017/12/watch-input-changes-angular-4.html
Prashobh
für Array-Änderungen: stackoverflow.com/questions/42962394/…
dasfdsa

Antworten:

720

Tatsächlich gibt es zwei Möglichkeiten, um zu erkennen und darauf zu reagieren, wenn sich eine Eingabe in der untergeordneten Komponente in angle2 + ändert:

  1. Sie können die Lebenszyklusmethode ngOnChanges () verwenden, wie auch in älteren Antworten erwähnt:
    @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

    }

Dokumentationslinks: ngOnChanges, SimpleChanges, SimpleChange
Demo Beispiel: Sehen Sie sich diesen Plunker an

  1. Alternativ können Sie auch einen Eingabe-Eigenschaften-Setter wie folgt verwenden:
    private _categoryId: string;

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

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

    }

    get categoryId(): string {

        return this._categoryId;

    }

Dokumentationslink: Schauen Sie hier .

Demo-Beispiel: Sehen Sie sich diesen Plunker an .

Welchen Ansatz sollten Sie verwenden?

Wenn Ihre Komponente mehrere Eingaben hat, erhalten Sie bei Verwendung von ngOnChanges () alle Änderungen für alle Eingaben gleichzeitig in ngOnChanges (). Mit diesem Ansatz können Sie auch aktuelle und vorherige Werte der geänderten Eingabe vergleichen und entsprechende Maßnahmen ergreifen.

Wenn Sie jedoch etwas tun möchten, wenn sich nur eine bestimmte Eingabe ändert (und Sie sich nicht um die anderen Eingaben kümmern), ist es möglicherweise einfacher, einen Eingabe-Eigenschaftssetzer zu verwenden. Dieser Ansatz bietet jedoch keine integrierte Möglichkeit, frühere und aktuelle Werte der geänderten Eingabe zu vergleichen (was mit der ngOnChanges-Lebenszyklusmethode problemlos möglich ist).

EDIT 2017-07-25: ANGULAR CHANGE DETECTION KANN UNTER EINIGEN UMSTÄNDEN NOCH NICHT FEUERN

Normalerweise wird die Änderungserkennung sowohl für Setter als auch für ngOnChanges ausgelöst, wenn die übergeordnete Komponente die Daten ändert, die an das untergeordnete Element übergeben werden, vorausgesetzt, die Daten sind ein primitiver JS-Datentyp (Zeichenfolge, Zahl, Boolescher Wert) . In den folgenden Szenarien wird es jedoch nicht ausgelöst und Sie müssen zusätzliche Maßnahmen ergreifen, damit es funktioniert.

  1. Wenn Sie ein verschachteltes Objekt oder Array (anstelle eines primitiven JS-Datentyps) verwenden, um Daten von Parent zu Child zu übergeben, wird die Änderungserkennung (entweder mit Setter oder ngchanges) möglicherweise nicht ausgelöst, wie auch in der Antwort des Benutzers: muetzerich erwähnt. Lösungen finden Sie hier .

  2. Wenn Sie Daten außerhalb des Winkelkontexts (dh extern) mutieren, kennt Angular die Änderungen nicht. Möglicherweise müssen Sie ChangeDetectorRef oder NgZone in Ihrer Komponente verwenden, um externe Änderungen im Winkel zu erkennen und damit die Änderungserkennung auszulösen. Beziehen Sie sich darauf .

Alan CS
quelle
8
Sehr guter Punkt in Bezug auf die Verwendung von Setzern mit Eingaben, ich werde dies aktualisieren, um die akzeptierte Antwort zu sein, da es umfassender ist. Vielen Dank.
Jon Catmull
2
@trichetriche, der Setter (Set-Methode) wird immer dann aufgerufen, wenn der Elternteil die Eingabe ändert. Gleiches gilt auch für ngOnChanges ().
Alan CS
Nun, anscheinend nicht ... Ich werde versuchen, das herauszufinden, es ist wahrscheinlich etwas falsch, was ich getan habe.
1
Ich bin gerade auf diese Frage gestoßen und habe festgestellt, dass Sie mit dem Setter keine Werte vergleichen können. Dies ist jedoch nicht der Fall. Sie können Ihre doSomethingMethode aufrufen und 2 Argumente für die neuen und alten Werte verwenden, bevor Sie den neuen Wert auf eine andere Weise festlegen würde den alten Wert speichern, bevor die Methode danach gesetzt und aufgerufen wird.
T.Aoukar
1
@ T.Aoukar Was ich damit sagen will ist, dass der Eingabesetzer den Vergleich alter / neuer Werte nicht wie ngOnChanges () unterstützt. Sie können jederzeit Hacks verwenden, um den alten Wert zu speichern (wie Sie bereits erwähnt haben), um ihn mit dem neuen Wert zu vergleichen.
Alan CS
105

Verwenden Sie die ngOnChanges()Lebenszyklusmethode in Ihrer Komponente.

ngOnChanges wird direkt nach dem Überprüfen der datengebundenen Eigenschaften und vor dem Überprüfen von untergeordneten Ansichts- und Inhaltseigenschaften aufgerufen, wenn mindestens eine davon geändert wurde.

Hier sind die Dokumente .

muetzerich
quelle
6
Dies funktioniert , wenn eine Variable auf einen neuen Wert , zB festgelegt MyComponent.myArray = [], aber wenn ein Eingabewert verändert zB ist MyComponent.myArray.push(newValue), ngOnChanges()wird nicht ausgelöst. Irgendwelche Ideen, wie diese Änderung erfasst werden kann?
Levi Fuller
4
ngOnChanges()wird nicht aufgerufen, wenn sich ein verschachteltes Objekt geändert hat. Vielleicht finden Sie eine Lösung hier
muetzerich
33

Bei der Verwendung des SimpleChangesTyps in der Funktionssignatur wurden Fehler in der Konsole sowie im Compiler und in der IDE angezeigt. Verwenden Sie anystattdessen das Schlüsselwort in der Signatur, um Fehler zu vermeiden .

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

BEARBEITEN:

Wie Jon weiter unten ausgeführt hat, können Sie die SimpleChangesSignatur verwenden, wenn Sie die Klammernotation anstelle der Punktnotation verwenden.

ngOnChanges(changes: SimpleChanges) {
    console.log(changes['myInput'].currentValue);
}
Darcy
quelle
1
Haben Sie die SimpleChangesBenutzeroberfläche oben in Ihrer Datei importiert ?
Jon Catmull
@ Jon - Ja. Das Problem war nicht die Signatur selbst, sondern der Zugriff auf die Parameter, die zur Laufzeit dem SimpleChanges-Objekt zugewiesen wurden. Zum Beispiel habe ich in meiner Komponente meine Benutzerklasse an die Eingabe (dh <my-user-details [user]="selectedUser"></my-user-details>) gebunden . Auf diese Eigenschaft wird von der SimpleChanges-Klasse durch Aufrufen zugegriffen changes.user.currentValue. Hinweis userist zur Laufzeit gebunden und nicht Teil des SimpleChanges-Objekts
Darcy
1
Hast du das schon versucht? ngOnChanges(changes: {[propName: string]: SimpleChange}) { console.log('onChanges - myProp = ' + changes['myProp'].currentValue); }
Jon Catmull
@Jon - Das Umschalten auf die Klammernotation reicht aus. Ich habe meine Antwort aktualisiert, um sie als Alternative aufzunehmen.
Darcy
1
{SimpleChanges} aus '@ angle / core' importieren; Wenn es sich in den eckigen Dokumenten befindet und nicht in einem nativen Typoskripttyp, dann stammt es wahrscheinlich aus einem eckigen Modul, höchstwahrscheinlich 'eckig / Kern'
BentOnCoding
13

Am sichersten ist es, einen gemeinsamen Dienst anstelle eines @InputParameters zu verwenden. Außerdem @Inputerkennt der Parameter keine Änderungen im komplexen verschachtelten Objekttyp.

Ein einfacher Beispieldienst lautet wie folgt:

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

Sie können eine ähnliche Implementierung in anderen Komponenten verwenden, und alle Ihre Komponenten haben dieselben gemeinsamen Werte.

Sanket Berde
quelle
1
Vielen Dank Sanket. Dies ist ein sehr guter Punkt und gegebenenfalls ein besserer Weg, dies zu tun. Ich werde die akzeptierte Antwort unverändert lassen, da sie meine spezifischen Fragen zum Erkennen von @ Input-Änderungen beantwortet.
Jon Catmull
1
Ein @ Input-Parameter erkennt keine Änderungen innerhalb eines komplexen verschachtelten Objekttyps, aber wenn sich das Objekt selbst ändert, wird es ausgelöst, oder? Beispiel: Ich habe den Eingabeparameter "parent". Wenn ich also das übergeordnete Element als Ganzes ändere, wird die Änderungserkennung ausgelöst, aber wenn ich parent.name ändere, wird dies nicht der Fall sein?
Yogibimbi
Sie sollten einen Grund angeben, warum Ihre Lösung sicherer ist
Joshua Kemmerer
1
Der @InputParameter @JoshuaKemmerer erkennt keine Änderungen am komplexen verschachtelten Objekttyp, jedoch an einfachen nativen Objekten.
Sanket Berde
6

Ich möchte nur hinzufügen, dass es einen weiteren Lifecycle-Hook DoCheckgibt, der nützlich ist, wenn der @InputWert kein primitiver Wert ist.

Ich habe ein Array als, Inputdaher wird das OnChangesEreignis nicht ausgelöst, wenn sich der Inhalt ändert (da die Überprüfung, die Angular durchführt, "einfach" und nicht tief ist, sodass das Array immer noch ein Array ist, obwohl sich der Inhalt des Arrays geändert hat).

Anschließend implementiere ich einen benutzerdefinierten Überprüfungscode, um zu entscheiden, ob ich meine Ansicht mit dem geänderten Array aktualisieren möchte.

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

Bitte versuchen Sie es mit dieser Methode. Hoffe das hilft

Akshay Rajput
quelle
4

Sie können auch eine Observable haben, die Änderungen in der übergeordneten Komponente (CategoryComponent) auslöst, und das tun, was Sie in der Subskription in der untergeordneten Komponente tun möchten. (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
});
}
Josf
quelle
2

Hier wird ngOnChanges immer ausgelöst, wenn sich Ihre Eingabeeigenschaft ändert:

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

Diese Lösung verwendet eine Proxy- Klasse und bietet die folgenden Vorteile:

  • Ermöglicht dem Verbraucher, die Leistung von RXJS zu nutzen
  • Mehr kompakter als andere Lösungen bisher vorgeschlagen
  • Mehr typsicher als die Verwendung vonngOnChanges()

Anwendungsbeispiel:

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

Dienstprogrammfunktion:

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;
}
Stephen Paul
quelle
0

Wenn Sie die ngOnChange-Implementierungsmethode og onChange () nicht verwenden möchten, können Sie Änderungen eines bestimmten Elements auch über das valueChanges-Ereignis ETC. abonnieren .

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

});

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

Das markForCheck () wurde aufgrund der Verwendung in dieser Deklaration geschrieben:

  changeDetection: ChangeDetectionStrategy.OnPush
user1012506
quelle
3
Dies unterscheidet sich grundlegend von der Frage, und obwohl hilfreiche Personen sich bewusst sein sollten, dass es sich bei Ihrem Code um eine andere Art von Änderung handelt. Die Frage zu Datenänderungen, die von AUSSERHALB einer Komponente stammen, und die Frage, ob Ihre Komponente die Tatsache, dass sich die Daten geändert haben, kennt und darauf reagiert. In Ihrer Antwort geht es darum, Änderungen innerhalb der Komponente selbst zu erkennen, beispielsweise durch Eingaben in einem Formular, die für die Komponente LOKAL sind.
rmcsharry
0

Sie können auch einfach einen EventEmitter als Eingabe übergeben. Ich bin mir nicht ganz sicher, ob dies die beste Vorgehensweise ist, obwohl ...

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>

Und in VideoListComponent.ts:

@Input() categoryIdEvent: EventEmitter<string>
....
ngOnInit() {
 this.categoryIdEvent.subscribe(newID => {
  this.categoryId = newID;
 }
}
Cédric Schweizer
quelle
Bitte geben Sie nützliche Links und Codefragmente an, um Ihre Lösung zu unterstützen.
Mischsx
Ich habe ein Beispiel hinzugefügt.
Cédric Schweizer