(ngModelChange) does not update the UI for specific input

Begin with a closer look at the ngModel directive API, you will see that ngModel is @Input binding which accepts some value as model variable. At the time of initializing ngModel control it creates FormControl implicitly.

public readonly control: FormControl = new FormControl();

The magic of updating model value happens from ngOnChanges hook, this way it syncs the view value with the model value. If you take a closer look at the ngOnChanges hook, you will find that it validates the input and applies other checks as well, afterwards it strictly checks if the value of ngModel has really changed using the isPropertyUpdated method.

ngOnChanges - ngModel directive

ngOnChanges(changes: SimpleChanges) {
    this._checkForErrors();
    if (!this._registered) this._setUpControl();
    if ('isDisabled' in changes) {
      this._updateDisabled(changes);
    }

    if (isPropertyUpdated(changes, this.viewModel)) {
      this._updateValue(this.model); // helps to update 
      this.viewModel = this.model;
    }
}

private _updateValue(value: any): void {
    resolvedPromise.then(() => { 
      // set value will set form control
      this.control.setValue(value, {emitViewToModelChange: false}); 
    });
}

But, to make it happen, Angular should recognize the changes during change detection cycle. And, since we haven't changed our model, it won't trigger the ngOnChanges hook:

enter image description here

What I explained till now was the API part. Lets come back to the question.

Try the below snippet in stackblitz, what our expectation would be. On input value change, it should set that value to 10 itself.

<input type="text" 
  [ngModel]="model.rate" 
  (ngModelChange)="model.rate = 10"
  name="rate" />

Unfortunately, it doesn't happen in that way, you will see that on initially typing any number, it will change the input value to 10 and later whatever you type will keep on appending to the number input. Ahh, wondering why?

Let's go back again to the original question,

<input type="text" 
  [ngModel]="model.rate" 
  (ngModelChange)="model.rate=roundRate($event)"
  name="rate" />
 {{model.rate}}

ngModel is used as a one way binding. Expected behavior is, whatever values assigned to the model.rate should be reflected inside the input. Let's try to enter 1.1, you will see that it shows us the value 1.1. Now try to enter 1.2, this results in 1. Wondering why? But certainly model.rate bindings update correctly. Similarly Check for 4.6 and 4.8. They result in 5, which works perfect.

Let's break down the above example, what happens when you try to enter 1.1.

  1. type 1 => model.rate becomes 1
  2. type 1. => model.rate stays 1
  3. type 1.1 => model.rate stays 1

Eventually when you type 1.1 or 1.2 the model value stays 1 since we Math.round the value. Since it isn't updating the model.rate value, whatever you enter further is just ignored by the ngModel binding, and is shown inside the input field.

Checking for 4.6, 4.8 works perfectly. Why? break it down step wise

  1. type 4 => model.rate becomes 4
  2. type 4. => model.rate stays 4
  3. type 4.6 => model.rate becomes 5

Over here, when you enter 4.6 in textbox, the Math.round value becomes 5. which is a change in the model.rate(ngModel) value and would result in an update in the input field value.

Now start reading the answer again, the technical aspect explained initially should be clear as well.

Note: While reading an answer follow the links provided to snippet, it may help you understand it more precisely.


To make it working you can try updating your fields on blur/change where this event fires on the focus out of fields like Sid's answer. It works because you're updating the model once when the field is focused out.

But it works only once. To keep updating constantly we can do a trick like this:

this.model.rate = new String(Math.round(value));

which will result in a new object reference each time we round our value.

Snippet in Stackblitz


In Angular change detection strategy help to reflect changes on UI.

Please use below code:

Step 1: Import ChangeDetectorRef in your component

import { ChangeDetectorRef} from angular/core';

Step 2: Create instance of ChangeDetectorRef on constructor.

constructor(private ref: ChangeDetectorRef){
}

Step 3: Call where you are updating value.

this.ref.detectChanges();

For a cleaner implementation, just use the (change) @Output property and [(ngModel)]. The implementation of roundRate will change something like this:

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  model = { rate: null };

  roundRate() {
    this.model.rate = Math.round(+this.model.rate);
  }
}

And in template:

<input type="text" [(ngModel)]="model.rate" (change)="roundRate()" name="rate" />

PS: This will update the value only once you blur from your input field


Here's a Sample StackBlitz for your ref.