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 becomes 1
- type
1. => model.rate stays 1
- 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
- type
4 => model.rate becomes 4
- type
4. => model.rate stays 4
- 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