Angular 5 realtime Change Detection in Directive

NavCore picture NavCore · Apr 9, 2018 · Viewed 7.4k times · Source

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');
    }
  }
}
  • In the above example I set subscription to @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');
}
  • I logged a message after each model change in a calculate() method.
  • A Directive is listening to ngModel changes and it will log a two messages for each model value change as well.

# 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
}
  • This way angular will change model and call change detection hook inside a Directive immediately.

# 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

Answer

NavCore picture NavCore · Apr 12, 2018

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