In AngularJS, we can listen variable change using $watch
, $digest
... which is no longer possible with the new versions of Angular (5, 6).
In Angular, this behaviour is now part of the component lifecycle.
I checked on the official documention, articles and especially on Angular 5 change detection on mutable objects , to find out how to listen to a variable (class property) change in a TypeScript class / Angular
What is proposed today is :
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 } }
This is only true for @Input()
property !
If I want to track changes of my component's own properties ( myProperty_1
, myProperty_2
or myProperty_3
), this will not work.
Can someone help me to solve this problematic ? Preferably a solution that is compatible with Angular 5
You can still check component's field members value change by 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);
});
}
}
see demo .
I think the nicest solution to your issue is to use a decorator that replaces the original field with a property automatically, then on the setter you can create a SimpleChanges
object similar to the one created by angular in order to use the same notification callback as for angular (alternatively you could create a different interface for these notifications, but the same principle applies)
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
as said you can use
public set myProperty_2(value: type): void {
if(value) {
//doMyCheck
}
this._myProperty_2 = value;
}
and then if you need to retrieve it
public get myProperty_2(): type {
return this._myProperty_2;
}
in that way you can do all the checks that you want while setting/ getting your variables such this methods will fire every time you set/get the myProperty_2 property.
small demo: https://stackblitz.com/edit/angular-n72qlu
I think I came into the way to listen to DOM changes that you can get any changes that do to your element, I really hope these hints and tips will help you to fix your problem, following the following simple step:
First , you need to reference your element like this:
in HTML:
<section id="homepage-elements" #someElement>
....
</section>
And in your TS file of that component:
@ViewChild('someElement')
public someElement: ElementRef;
Second , you need to create an observer to listen to that element changes, you need to make your component ts
file to implements AfterViewInit, OnDestroy
, then implement that ngAfterViewInit()
there ( OnDestroy
has a job later):
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
});
}
You will see the console will work with any changes on that element:
This is another example here that you will see 2 mutation records fired and for the class that changed:
// 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);
}
});
}
);
Console log:
And just housekeeping stuff, OnDestroy
:
ngOnDestroy(): void {
this.changes.disconnect();
}
Finally, you can look into this Reference: Listening to DOM Changes Using MutationObserver in Angular
You can import 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();
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.