I'm pretty much informed of how Angular's Change Detection works, as well as how we can use OnChanges hook for detecting @Input
properties changes, and also a subscribing to ngModel valueChanges in for example a Directive or a Component etc..
Can anyone exaplain what is happening here:
# Custom Directive:
Let's say we have a custom Directive myNumber which has an @Input() property ngModel:
@Directive({
selector: "[myNumber]"
})
class MyNumberDirective implements OnChanges {
@Input() ngModel: any;
constructor(private model: NgModel) {
this.model.control.valueChanges.subscribe(data => {
console.log('directive model changes detected by model control value change subscription');
});
}
ngOnChanges(changes: SimpleChanges){
if(changes.ngModel){
console.log('directive input ngModel changes detected by OnChanges hook');
}
}
}
@Input
property ngModel and directive's model object changes. Changes should be logged in the console when model value changes.# Component's template:
<input type="number" myNumber [(ngModel)]="number1" />
<input type="number" myNumber [(ngModel)]="number2" />
<input type="number" myNumber [(ngModel)]="number3" (blur)="calculate()" />
We applied myNumber directive on three input elements and each input element has ngModel: number1, number2, number3.
Last input has on blur event to call calculate() method.
# Component's typescript:
calculate() {
this.number1 = 10; // changing ngModel of first input
console.log('number1 changed in a calculate method');
this.number2 = 20; // changing ngModel of second input
console.log('number2 changed in a calculate method');
this.number3 = 30; // changing ngModel of third input
console.log('number3 changed in a calculate method');
}
# Problem:
Angular will execute calculate()
method, change all three models and then detect changes and trigger cd hooks in a Directive:
// calculate() log messages first:
'number1 changed in a calculate method'
'number2 changed in a calculate method'
'number3 changed in a calculate method'
// then number1 model change messages in a directive:
'directive model changes detected by model control value change subscription'
'directive input ngModel changes detected by OnChanges hook'
// then number2 model change messages in a directive:
'directive model changes detected by model control value change subscription'
'directive input ngModel changes detected by OnChanges hook'
// then number3 model change messages in a directive:
'directive model changes detected by model control value change subscription'
'directive input ngModel changes detected by OnChanges hook'
# Solution I would like to simplify:
In the component we can call changeDetection()
after each model change in the calculate()
method. That will trigger directive's change detection hook automatically.
constructor(private ref: ChangeDetectorRef) {}
calculate() {
this.number1 = 10; // changing ngModel of first input
console.log('number1 changed in a calculate method');
this.ref.detectChanges(); // triggering detect changes manually
this.number2 = 20; // changing ngModel of second input
console.log('number2 changed in a calculate method');
this.ref.detectChanges(); // triggering detect changes manually
this.number3 = 30; // changing ngModel of third input
console.log('number3 changed in a calculate method');
this.ref.detectChanges(); // triggering detect changes manually
}
# Question:
How to achieve this immediately and change detections without manually writing ref.detectChanges()
after each model change ?
I hope this example will be useful to all you guys having the same problems
To run the change detector manually: Inject ChangeDetectorRef service in the component. Use markForCheck in the subscription method to instruct Angular to check the component the next time change detectors run. On the ngOnDestroy() life cycle hook, unsubscribe from the observable.
detectChanges — When you call this method on changeDetectorRef provider, it will run change detection from the current component and all it's descendants. While running change detection it keeps the change detection strategy in mind.
detectChanges()linkChecks this view and its children. Use in combination with detach to implement local change detection checks.
By default, angular will run the change detector every time @Input() data is changed or modified. But with OnPush strategy, the change detector is only triggered if the data passed on @Input() has a new reference.
Update:
The above example in my first post is actually working :) it was my mistake and I will explain it in detail.
In my real-world implementation I'm applying number directive to all number input fields. A directive is listening on blur model changes and applying some number rounding.
Everything works fine -> we insert a number and onBlur number will be rounded, applied pipe, etc..
The problem was when I had additional calculation method to re-calculate another fields:
For example:
calculate() {
this.number1 = 10; // changing ngModel of number1
// after model change number1 will be rounded (handled) in a directive
const number2 = 20; // this is not a model so it's not handled by directive
// I used non-model variable in my calculation that is not handled by directive
this.number3 = this.number1 * number2; // changing ngModel of number3
}
By using non-model variable in a calculation - that value is not rounded so I had mismatches in method vs directive calculation rounding resulting in a slightly different number.
That's why I thought that Angular (directive) is not detecting changes in a right moment.
Sorry guys for that, but I hope this example of subscribing to detect changes will help someone !!
:: cheers ::
Josip
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With