Quel est l'équivalent angulaire d'une montre AngularJS $?

213

Dans AngularJS, vous pouviez spécifier des observateurs pour observer les changements dans les variables de portée en utilisant la $watchfonction de $scope. Quel est l'équivalent de surveiller les changements de variables (dans, par exemple, les variables de composants) dans Angular?

Erwin
la source
Utilisez get accessor en tapuscrit.
jmvtrinidad
consultez cet article qui explique la différence
Max Koretskyi

Réponses:

268

Dans Angular 2, la détection des changements est automatique ... $scope.$watch()et $scope.$digest()RIP

Malheureusement, la section Détection des changements du guide de développement n'est pas encore écrite (il y a un espace réservé au bas de la page Présentation de l' architecture , dans la section "Les autres choses").

Voici ma compréhension du fonctionnement de la détection des modifications:

  • Zone.js "monkey patches the world" - il intercepte toutes les API asynchrones dans le navigateur (lorsque Angular s'exécute). C'est pourquoi nous pouvons utiliser à l' setTimeout()intérieur de nos composants plutôt que quelque chose comme $timeout... parce que le setTimeout()singe est patché.
  • Angular construit et maintient un arbre de "détecteurs de changement". Il existe un tel détecteur de changement (classe) par composant / directive. (Vous pouvez accéder à cet objet en injectant ChangeDetectorRef.) Ces détecteurs de changement sont créés lorsque Angular crée des composants. Ils gardent une trace de l'état de toutes vos fixations, pour un contrôle sale. Celles-ci sont, dans un sens, similaires à l'automatique $watches()qu'Angular 1 mettrait en place pour {{}}les liaisons de modèles.
    Contrairement à Angular 1, le graphique de détection de changement est un arbre dirigé et ne peut pas avoir de cycles (cela rend Angular 2 beaucoup plus performant, comme nous le verrons ci-dessous).
  • Lorsqu'un événement se déclenche (à l'intérieur de la zone angulaire), le code que nous avons écrit (le rappel du gestionnaire d'événements) s'exécute. Il peut mettre à jour toutes les données qu'il souhaite - le modèle / l'état de l'application partagée et / ou l'état de vue du composant.
  • Après cela, en raison des hooks Zone.js ajoutés, il exécute ensuite l'algorithme de détection de changement d'Angular. Par défaut (c.-à-d. Si vous n'utilisez la onPushstratégie de détection des modifications sur aucun de vos composants), chaque composant de l'arborescence est examiné une fois (TTL = 1) ... à partir du haut, dans le premier ordre de profondeur. (Eh bien, si vous êtes en mode dev, la détection des modifications s'exécute deux fois (TTL = 2). Voir ApplicationRef.tick () pour plus d'informations.)
    • Les hooks du cycle de vie sont appelés dans le cadre de la détection des modifications.
      Si les données de composant que vous souhaitez regarder sont une propriété d'entrée primitive (chaîne, booléenne, nombre), vous pouvez l'implémenter ngOnChanges()pour être averti des modifications.
      Si la propriété d'entrée est un type de référence (objet, tableau, etc.), mais que la référence n'a pas changé (par exemple, vous avez ajouté un élément à un tableau existant), vous devrez l'implémenter ngDoCheck()(voir cette réponse SO pour plus d'informations) sur ce).
      Vous ne devez modifier que les propriétés du composant et / ou les propriétés des composants descendants (en raison de l'implémentation de l'arborescence unique - c'est-à-dire du flux de données unidirectionnel). Voici un plongeur qui viole cela. Des tuyaux dynamiques peuvent également vous faire trébucher ici.
  • Pour toutes les modifications de liaison trouvées, les composants sont mis à jour, puis le DOM est mis à jour. La détection des modifications est maintenant terminée.
  • Le navigateur remarque les changements DOM et met à jour l'écran.

Autres références pour en savoir plus:

Mark Rajcok
la source
window.addEventListener () ne déclenche pas la détection lorsque les variables sont modifiées ... cela me rend fou, il n'y a rien là-dessus.
Albert James Teddy
@AlbertJamesTeddy, voir la documentation host"Host Listeners" dans le document API DirectiveMetadata . Il explique comment écouter les événements globaux depuis l'intérieur de la zone angulaire (donc la détection de changement sera déclenchée comme vous le souhaitez). Cette réponse a un plongeur qui fonctionne.
Mark Rajcok
ce lien serait utile ..
refactor
@MarkRajcok, j'ai pris la liberté d'ajouter une référence à mon article sur la détection des changements. J'espère que cela ne vous dérange pas. Il explique en détail ce qui se passe sous le capot.
Max Koretskyi
Concernant le plunkr qui viole la règle de flux de données unidirectionnel, je voudrais ajouter que si vous exécutez le plunkr avec enableProdMode (), vous ne verrez aucune mise à jour dans la vue parent, car le détecteur de changement ne s'exécute qu'une seule fois.
Mister_L
93

Ce comportement fait désormais partie du cycle de vie des composants.

Un composant peut implémenter la méthode ngOnChanges dans l' interface OnChanges pour accéder aux modifications d'entrée.

Exemple:

import {Component, Input, OnChanges} from 'angular2/core';


@Component({
  selector: 'hero-comp',
  templateUrl: 'app/components/hero-comp/hero-comp.html',
  styleUrls: ['app/components/hero-comp/hero-comp.css'],
  providers: [],
  directives: [],

  pipes: [],
  inputs:['hero', 'real']
})
export class HeroComp implements OnChanges{
  @Input() hero:Hero;
  @Input() real:string;
  constructor() {
  }
  ngOnChanges(changes) {
      console.log(changes);
  }
}
toskv
la source
77
cela n'est vrai que pour @Input (). si vous souhaitez suivre les modifications des données de votre composant, cela ne fonctionnera pas
LanderV
4
Je n'ai pas pu obtenir de changements de variables simples (booléennes par exemple). Seuls les changements d'objets sont détectés.
mtoloo
pourquoi avez-vous besoin d'ajouter un tableau "d'entrées" dans le décorateur du composant? la détection des modifications fonctionnera également sans cela.
Gil Epshtain
68

Si, en plus de la liaison bidirectionnelle automatique, vous souhaitez appeler une fonction lorsqu'une valeur change, vous pouvez casser la syntaxe de raccourci de la liaison bidirectionnelle vers la version plus détaillée.

<input [(ngModel)]="yourVar"></input>

est un raccourci pour

<input [ngModel]="yourVar" (ngModelChange)="yourVar=$event"></input>

(voir par exemple http://victorsavkin.com/post/119943127151/angular-2-template-syntax )

Vous pouvez faire quelque chose comme ça:

<input [(ngModel)]="yourVar" (ngModelChange)="changedExtraHandler($event)"></input>

supermikko
la source
Dans le dernier exemple, vous vouliez supprimer [] autour de ngModel?
Eugene Kulabuhov
16

Vous pouvez utiliser getter functionou get accessorpour agir comme montre sur angulaire 2.

Voir la démo ici .

import {Component} from 'angular2/core';

@Component({
  // Declare the tag name in index.html to where the component attaches
  selector: 'hello-world',

  // Location of the template for this component
  template: `
  <button (click)="OnPushArray1()">Push 1</button>
  <div>
    I'm array 1 {{ array1 | json }}
  </div>
  <button (click)="OnPushArray2()">Push 2</button>
  <div>
    I'm array 2 {{ array2 | json }}
  </div>
  I'm concatenated {{ concatenatedArray | json }}
  <div>
    I'm length of two arrays {{ arrayLength | json }}
  </div>`
})
export class HelloWorld {
    array1: any[] = [];
    array2: any[] = [];

    get concatenatedArray(): any[] {
      return this.array1.concat(this.array2);
    }

    get arrayLength(): number {
      return this.concatenatedArray.length;
    }

    OnPushArray1() {
        this.array1.push(this.array1.length);
    }

    OnPushArray2() {
        this.array2.push(this.array2.length);
    }
}
jmvtrinidad
la source
12

Voici une autre approche utilisant les fonctions getter et setter pour le modèle.

@Component({
  selector: 'input-language',
  template: `
  …
  <input 
    type="text" 
    placeholder="Language" 
    [(ngModel)]="query" 
  />
  `,
})
export class InputLanguageComponent {

  set query(value) {
    this._query = value;
    console.log('query set to :', value)
  }

  get query() {
    return this._query;
  }
}
sélectionner
la source
4
Ce sujet est fou. J'ai un objet avec de nombreuses propriétés liées à une forme complexe. Je ne veux pas ajouter de (change)gestionnaire (s) sur chacun d'entre eux; Je ne veux pas ajouter de get|setss à chaque propriété de mon modèle; il ne sera pas utile d'ajouter un get|setpour this.object; ngOnChanges() détecte uniquement les modifications apportées à l' @Inputart . Sainte manne! Que nous ont-ils fait ??? Rendez-nous une sorte de veille profonde!
Cody
6

Si vous souhaitez le rendre contraignant dans les deux sens, vous pouvez l'utiliser [(yourVar)], mais vous devez implémenter l' yourVarChangeévénement et l'appeler à chaque fois que votre variable change.

Quelque chose comme ça pour suivre le changement de héros

@Output() heroChange = new EventEmitter();

puis quand votre héros est changé, appelez this.heroChange.emit(this.hero);

la [(hero)]reliure fera le reste pour vous

voir l'exemple ici:

http://plnkr.co/edit/efOGIJ0POh1XQeRZctSx?p=preview

Mohy Eldeen
la source
2

Cela ne répond pas directement à la question, mais j'ai à plusieurs reprises atterri sur cette question de débordement de pile afin de résoudre quelque chose que j'utiliserais $ watch for dans angularJs. J'ai fini par utiliser une autre approche que celle décrite dans les réponses actuelles, et je veux la partager au cas où quelqu'un la trouverait utile.

La technique que j'utilise pour réaliser quelque chose de similaire $watchest d'utiliser un BehaviorSubject( plus sur le sujet ici ) dans un service Angular, et de laisser mes composants s'y abonner afin d'obtenir (regarder) les changements. Ceci est similaire à un $watchdans angularJs, mais nécessite plus de configuration et de compréhension.

Dans ma composante:

export class HelloComponent {
  name: string;
  // inject our service, which holds the object we want to watch.
  constructor(private helloService: HelloService){
    // Here I am "watching" for changes by subscribing
    this.helloService.getGreeting().subscribe( greeting => {
      this.name = greeting.value;
    });
  }
}

À mon service

export class HelloService {
  private helloSubject = new BehaviorSubject<{value: string}>({value: 'hello'});
  constructor(){}
  // similar to using $watch, in order to get updates of our object 
  getGreeting(): Observable<{value:string}> {
    return this.helloSubject;
  }
  // Each time this method is called, each subscriber will receive the updated greeting.
  setGreeting(greeting: string) {
    this.helloSubject.next({value: greeting});
  }
}

Voici une démo sur Stackblitz

John
la source