web-dev-qa-db-fra.com

Comment écouter les changements de valeur de la propriété de classe TypeScript - Angular

Dans AngularJS, nous pouvons écouter le changement de variable en utilisant $watch, $digest... qui n’est plus possible avec les nouvelles versions de Angular (5, 6).

Dans Angular, ce comportement fait maintenant partie du cycle de vie du composant.

J'ai vérifié la documentation officielle, les articles et surtout sur Détection de changements angulaires 5 sur des objets mutables , pour savoir comment écouter un changement de variable (propriété de classe) dans une classe TypeScript / Angulaire

Ce qui est proposé aujourd'hui, c'est: 

import { OnChanges, SimpleChanges, DoCheck } from '@angular/core';


@Component({
  selector: 'my-comp',
  templateUrl: 'my-comp.html',
  styleUrls: ['my-comp.css'],
  inputs:['input1', 'input2']
})
export class MyClass implements OnChanges, DoCheck, OnInit{

  //I can track changes for this properties
  @Input() input1:string;
  @Input() input2:string;
  
  //Properties what I want to track !
  myProperty_1: boolean
  myProperty_2: ['A', 'B', 'C'];
  myProperty_3: MysObject;

  constructor() { }

  ngOnInit() { }

  //Solution 1 - fired when Angular detects changes to the @Input properties
  ngOnChanges(changes: SimpleChanges) {
    //Action for change
  }

  //Solution 2 - Where Angular fails to detect the changes to the input property
  //the DoCheck allows us to implement our custom change detection
  ngDoCheck() {
    //Action for change
  }
}

Ceci n'est vrai que pour la propriété @Input()

Si je souhaite suivre les modifications des propriétés de mon composant (myProperty_1, myProperty_2 ou myProperty_3), cela ne fonctionnera pas.

Quelqu'un peut-il m'aider à résoudre cette problématique? De préférence, une solution compatible avec Angular 5

6
Lyes CHIOUKH

Vous pouvez toujours vérifier la valeur de changement des membres du champ du composant de KeyValueDiffers via DoCheck lifehook.

import { DoCheck, KeyValueDiffers, KeyValueDiffer } from '@angular/core';

differ: KeyValueDiffer<string, any>;;
constructor(private differs: KeyValueDiffers) {
  this.differ = this.differs.find({}).create();
}

ngDoCheck() {
  const change = this.differ.diff(this);
  if (change) {
    change.forEachChangedItem(item => {
      console.log('item changed', item);
    });
  }
}

voir demo.

4
Pengyy

Je pense que la meilleure solution à votre problème consiste à utiliser un décorateur qui remplace automatiquement le champ d'origine par une propriété. Vous pouvez ensuite créer un objet SimpleChanges similaire à celui créé par angular afin d'utiliser le même rappel de notification que pour angular (vous pouvez également créer une interface différente pour ces notifications, mais le même principe s'applique)

import { OnChanges, SimpleChanges, DoCheck, SimpleChange } from '@angular/core';

function Watch() : PropertyDecorator & MethodDecorator{
    function isOnChanges(val: OnChanges): val is OnChanges{
        return !!(val as OnChanges).ngOnChanges
    }
    return (target : any, key: string | symbol, propDesc?: PropertyDescriptor) => {
        let privateKey = "_" + key.toString();
        let isNotFirstChangePrivateKey = "_" + key.toString() + 'IsNotFirstChange';
        propDesc = propDesc || {
            configurable: true,
            enumerable: true,
        };
        propDesc.get = propDesc.get || (function (this: any) { return this[privateKey] });

        const originalSetter = propDesc.set || (function (this: any, val: any) { this[privateKey] = val });

        propDesc.set = function (this: any, val: any) {
            let oldValue = this[key];
            if(val != oldValue) {
                originalSetter.call(this, val);
                let isNotFirstChange = this[isNotFirstChangePrivateKey];
                this[isNotFirstChangePrivateKey] = true;
                if(isOnChanges(this)) {
                    var changes: SimpleChanges = {
                        [key]: new SimpleChange(oldValue, val, !isNotFirstChange)
                    }
                    this.ngOnChanges(changes);
                }
            }
        }
        return propDesc;
    }
}

// Usage
export class MyClass implements OnChanges {


    //Properties what I want to track !
    @Watch()
    myProperty_1: boolean  =  true
    @Watch()
    myProperty_2 =  ['A', 'B', 'C'];
    @Watch()
    myProperty_3 = {};

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

var myInatsnce = new MyClass(); // outputs original field setting with firstChange == true
myInatsnce.myProperty_2 = ["F"]; // will be notified on subsequent changes with firstChange == false

comme dit vous pouvez utiliser 

public set myProperty_2(value: type): void {
 if(value) {
  //doMyCheck
 }

 this._myProperty_2 = value;
}

et puis si vous avez besoin de le récupérer 

public get myProperty_2(): type {
  return this._myProperty_2;
}

de cette manière, vous pouvez effectuer toutes les vérifications que vous souhaitez lors de la définition/obtention de vos variables. Ces méthodes sont activées chaque fois que vous définissez/obtenez la propriété myProperty_2.

petite démo: https://stackblitz.com/edit/angular-n72qlu

2
Vale Steve

Je pense que je suis venu pour écouter les modifications du DOM et que vous pouvez obtenir les modifications qui affectent votre élément. J'espère vraiment que ces astuces vous aideront à résoudre votre problème en suivant la simple étape suivante:

First, vous devez référencer votre élément de la manière suivante:

en HTML:

<section id="homepage-elements" #someElement>
....
</section>

Et dans votre fichier TS de ce composant:

@ViewChild('someElement')
public someElement: ElementRef;

Second, vous devez créer un observateur pour écouter ce changement d'élément. Vous devez également transformer votre fichier ts de composant en implements AfterViewInit, OnDestroy, puis implémenter cette ngAfterViewInit() dans cet élément (OnDestroy ayant un travail ultérieur):

private changes: MutationObserver;
ngAfterViewInit(): void {
  console.debug(this.someElement.nativeElement);

  // This is just to demo 
  setInterval(() => {
    // Note: Renderer2 service you to inject with constructor, but this is just for demo so it is not really part of the answer
    this.renderer.setAttribute(this.someElement.nativeElement, 'my_custom', 'secondNow_' + (new Date().getSeconds()));
  }, 5000);

  // Here is the Mutation Observer for that element works
  this.changes = new MutationObserver((mutations: MutationRecord[]) => {
      mutations.forEach((mutation: MutationRecord) => {
        console.debug('Mutation record fired', mutation);
        console.debug(`Attribute '${mutation.attributeName}' changed to value `, mutation.target.attributes[mutation.attributeName].value);
      });
    }
  );

  // Here we start observer to work with that element
  this.changes.observe(this.someElement.nativeElement, {
    attributes: true,
    childList: true,
    characterData: true
  });
}

Vous verrez que la console fonctionnera avec toutes les modifications apportées à cet élément:

 enter image description here

Voici un autre exemple où vous verrez 2 enregistrements de mutation tirés et pour la classe qui a changé:

// This is just to demo
setTimeout(() => {
   // Note: Renderer2 service you to inject with constructor, but this is just for demo so it is not really part of the answer
  this.renderer.addClass(this.someElement.nativeElement, 'newClass' + (new Date().getSeconds()));
  this.renderer.addClass(this.someElement.nativeElement, 'newClass' + (new Date().getSeconds() + 1));
}, 5000);

// Here is the Mutation Observer for that element works
this.changes = new MutationObserver((mutations: MutationRecord[]) => {
    mutations.forEach((mutation: MutationRecord) => {
      console.debug('Mutation record fired', mutation);
      if (mutation.attributeName == 'class') {
        console.debug(`Class changed, current class list`, mutation.target.classList);
      }
    });
  }
);

Journal de la console:

 enter image description here

Et juste des trucs de ménage, OnDestroy:

ngOnDestroy(): void {
  this.changes.disconnect();
}

Enfin, vous pouvez examiner cette référence: Écoute de modifications dans le DOM à l'aide de MutationObserver in Angular

1
Al-Mothafar

Vous pouvez importer ChangeDetectorRef

 constructor(private cd: ChangeDetectorRef) {
          // detect changes on the current component
            // this.cd is an injected ChangeDetector instance
            this.cd.detectChanges();

            // or run change detection for the all app
            // this.appRef is an ApplicationRef instance
            this.appRef.tick();
}
0
Sultan Khan