web-dev-qa-db-fra.com

Comment détecter quand une valeur @Input () change dans Angular?

J'ai un composant parent ( CategoryComponent ), un composant enfant ( videoListComponent ) et un ApiService.

La plupart de ces choses fonctionnent très bien, c’est-à-dire que chaque composant peut accéder à l’API JSON et obtenir ses données pertinentes via des observables.

Actuellement, le composant de liste de vidéos ne reçoit que toutes les vidéos, je voudrais le filtrer uniquement aux vidéos d'une catégorie particulière. Pour ce faire, j'ai passé la catégorie Id à l'enfant via @Input().

CategoryComponent.html

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

Cela fonctionne et lorsque la catégorie CategoryComponent parent change, la valeur categoryId est transmise via @Input() mais je dois ensuite détecter cela dans VideoListComponent et redemander le tableau de vidéos via APIService (avec le nouveau categoryId).

Dans AngularJS, j'aurais fait un $watch sur la variable. Quelle est la meilleure façon de gérer cela?

261
Jon Catmull

En fait, il y a deux façons de détecter et d'intervenir lorsqu'une entrée change dans la composante enfant dans angular2 +:

  1. Vous pouvez utiliser la méthode du cycle de vie ngOnChanges () comme indiqué dans les réponses précédentes:
    @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

    }

Liens de documentation: ngOnChanges,SimpleChanges,SimpleChange
Exemple de démonstration: Regardez ce plongeur

  1. Alternativement, vous pouvez également utiliser un input property setter comme suit:
    private _categoryId: string;

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

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

    }

    get categoryId(): string {

        return this._categoryId;

    }

Lien vers la documentation: Regardez ici

Exemple de démonstration: Regardez ce plongeur .

Quelle approche devriez-vous utiliser?

Si votre composant a plusieurs entrées, si vous utilisez ngOnChanges (), vous obtiendrez toutes les modifications pour toutes les entrées en même temps dans ngOnChanges (). En utilisant cette approche, vous pouvez également comparer les valeurs actuelles et précédentes de l'entrée modifiée et prendre des mesures en conséquence.

Cependant, si vous voulez faire quelque chose quand seule une entrée particulière change (et que vous ne vous souciez pas des autres entrées), il pourrait être plus simple d'utiliser un outil de définition de propriété d'entrée. Cependant, cette approche ne fournit pas de méthode intégrée pour comparer les valeurs précédentes et actuelles de l'entrée modifiée (ce que vous pouvez facilement faire avec la méthode du cycle de vie ngOnChanges).

EDIT 2017-07-25: LA DÉTECTION DE CHANGEMENT ANGULAIRE PEUT NE PAS ÊTRE INCENDIE DANS CERTAINES CIRCONSTANCES

Normalement, la détection des modifications pour setter et ngOnChanges se déclenche chaque fois que le composant parent modifie les données transmises à l'enfant, à condition que les données soient un type de données primitif JS (chaîne, nombre, booléen). Toutefois, dans les scénarios suivants, il ne se déclenchera pas et vous devrez prendre des mesures supplémentaires pour le faire fonctionner.

  1. Si vous utilisez un objet ou un tableau imbriqué (au lieu d'un type de données primitif JS) pour transmettre des données de parent à enfant, la détection de modification (à l'aide de setter ou de ngchanges) peut ne pas se déclencher, comme indiqué également dans la réponse de l'utilisateur: muetzerich. Pour des solutions, regardez ici

  2. Si vous modifiez des données en dehors du contexte angulaire (c'est-à-dire extérieurement), alors angular ne saura rien des changements. Il se peut que vous deviez utiliser ChangeDetectorRef ou NgZone dans votre composant pour rendre angulaire des modifications externes et ainsi déclencher la détection des modifications. Reportez-vous à this .

377
Alan C. S.

Utilisez la méthode du cycle de vie ngOnChanges() dans votre composant.

ngOnChanges est appelé juste après que les propriétés liées aux données ont été coché et avant la vue et le contenu, les enfants sont cochés si au moins l'un d'eux a changé.

Voici les Docs .

89
muetzerich

J'avais des erreurs dans la console ainsi que dans le compilateur et IDE lorsque le type SimpleChanges était utilisé dans la signature de fonction. Pour éviter les erreurs, utilisez plutôt le mot clé any dans la signature.

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

MODIFIER:

Comme Jon l'a souligné ci-dessous, vous pouvez utiliser la signature SimpleChanges lorsque vous utilisez la notation entre crochets plutôt que la notation pointée.

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

Le pari le plus sûr consiste à utiliser un service partagé à la place d'un paramètre @Input . En outre, le paramètre @Input ne détecte pas les modifications apportées au type d'objet imbriqué complexe.

Un exemple de service simple est le suivant:

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

Vous pouvez utiliser une implémentation similaire dans d'autres composants et tous vos composants partageront les mêmes valeurs partagées.

6
Sanket Berde

Vous pouvez également avoir un observable qui déclenche des changements dans le composant parent (CategoryComponent) et faire ce que vous voulez faire dans la souscription dans le composant enfant. (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
});
}
1
Josf

Je veux juste ajouter qu'il existe un autre hook Lifecycle appelé DoCheck qui est utile si la valeur @Input n'est pas une valeur primitive.

J'ai un tableau en tant que Input afin que l'événement OnChanges ne soit pas déclenché lorsque le contenu change (car la vérification effectuée par Angular est "simple" et non profonde, de sorte que le tableau est toujours un tableau, même si le contenu du tableau a été modifié. ).

J'implémente ensuite un code de vérification personnalisé pour décider si je souhaite mettre à jour ma vue avec le tableau modifié. 

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

s'il vous plaît essayez d'utiliser cette méthode. J'espère que cela t'aides

0
Akshay Rajput
0
Emmanuel John

Ici, ngOnChanges sera toujours déclenché lorsque votre propriété d’entrée sera modifiée:

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

Si vous ne souhaitez pas utiliser la méthode ngOnChange Implement og onChange (), vous pouvez également vous abonner aux modifications d'un élément spécifique par valueChanges event , ETC.

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

});

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

le markForCheck () écrit en raison de l'utilisation dans cette déclaration:

  changeDetection: ChangeDetectionStrategy.OnPush
0
user1012506