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