(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:
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
.
- type
1
=> model.rate becomes1
- type
1.
=> model.rate stays1
- type
1.1
=> model.rate stays1
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
- type
4
=> model.rate becomes4
- type
4.
=> model.rate stays4
- type
4.6
=> model.rate becomes5
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.