Reputation: 101
I have the following input
<input type="number" [ngModel]="inputValue" (ngModelChange)="handleChange($event)"/>
And I am trying to force the input to stay less or equal to 100. using the method handleChange
export class AppComponent {
inputValue: number = 0;
handleChange($event: any) {
if ($event > 100) {
this.inputValue = 100;
}
}
}
And it only works the first time I enter the first input that is higher than 100, but after that it doesn't.
My understanding of it is that the DOM doesn't update when there are no value updates.
I can solve this problem using other methods like @ViewChild for example, but I am more interested in knowing how this works and how angular handles this specific use case.
Thanks!
Upvotes: 6
Views: 1895
Reputation: 1966
The other answers already explain it pretty well. Like Chris said:
The problem is you never changed inputValue away from 100 - so every time you set it to 100 - the change detector sees no change. It will never update the html again.
So basically you have a conceptual issue, because you don't update inputValue, if the input is less or equal to 100. So you should update the inputValue in that case (always). This fixes the behaviour up to 100, but doesn't work above 100 though, since it keeps it at 100.
ChangeDetectorRef should work, but for completness sake, I am adding the quick and dirty setTimeout way.
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
inputValue = 0;
handleChange(event: number): void {
this.inputValue = event;
if (event > 100) {
setTimeout(() => this.inputValue = 100); // Use the "force" with caution, young Jedi. ;-)
}
}
}
EDIT: removed the ms delay, like Chris suggested.
Upvotes: 2
Reputation: 10964
The solution is to introduce two-way binding, so your property and the html value are synchronized. You also need to force change detection before setting the property to 100.
Stackblitz: https://stackblitz.com/edit/angular-ivy-upykps?file=src/app/app.component.ts
Both of these steps are necessary for the change detector to detect that the property has changed every time, only then will it update the html value with your property value.
Two way binding is accomplished with the banana-in-a-box [(🍌)]
notation.
Change detection is forced with the ChangeDetectorRef
injectable service.
<input type="number" [(ngModel)]="inputValue" (ngModelChange)="handleChange($event)"/>
inputValue: number = 0;
constructor(private cd: ChangeDetectorRef){};
handleChange($event: any) {
this.cd.detectChanges();
if ($event > 100) {
this.inputValue = 100;
}
}
I know this is a bit unintuitive but it has to do with how the change detector detects changes. If you really want to understand it, I suggest adding a debugger
line at the start of your function and walking through the steps.
I'll explain what was happening the way you had it set up:
The html value is set to 0 via data binding and the change detector notes that the property is currently 0. On every click event, change detection is run after the ngModelChange
callback. So, the first time you set the property to 100, the change detector sees - oh yes, the property used to be 0, now it is 100, a change has occurred - it sets a flag that causes the html to be updated with the new value, and notes that the property is currently 100.
The problem is you never changed inputValue
away from 100 - so every time you set it to 100 - the change detector sees no change. It will never update the html again.
Using two-way binding, the property is updated whenever the html value is changed - this happens before ngModelChange
. But this does not update the change detector's state! It will work when we go from less than 100 to greater than 100, but if we continue passing more numbers that are larger than 100, it won't be reset. That's because change detection is run after ngModelChange
, and we actually overwrite the larger value to 100 before the change detector can see the larger value. We get the same problem - the change detector sees 100 every time and does not attempt to update the html.
You need the forced change detection so the change detector can note down that the property changed to this larger value. Then after setting the property to 100, it will see - ah yes, the property used to be this larger value, now it is 100 - it sets the "changed" flag which triggers a DOM update.
An example when the user changes the html from 100 to 101:
The change detector notes the current state on every change detection, but NOT during data binding. DOM updates are only triggered when the change detector finds a change.
Initial State
ACTUAL
html value: 100
property: 100
CHANGE DETECTOR
property: 100
User changes html value to 101
ACTUAL
html value: 101
property: 100
CHANGE DETECTOR
property: 100
Round bracket data binding updates property
ACTUAL
html value: 101
property: 101
CHANGE DETECTOR
property: 100
handleChange calls ChangeDetectorRef.detectChanges()
ACTUAL
html value: 101
property: 101
CHANGE DETECTOR
property: 100 - CHANGED!
**This is the important part
the change detector sees that the property changed to 101**
Square bracket data binding attempts to update html because of a change
It's already correct though
ACTUAL
html value: 101
property: 101
CHANGE DETECTOR
property: 101
handleChange changes property to 100
ACTUAL
html value: 101
property: 100
CHANGE DETECTOR
property: 101
change detector detects changes because click event occurred
ACTUAL
html value: 101
property: 100
CHANGE DETECTOR
property: 101 - CHANGED!
**If this was still at 100, no updates would be made.
There would be a mismatch between property and html value**
Square bracket data binding updates html because of a change
ACTUAL
html value: 100
property: 100
CHANGE DETECTOR
property: 100
Upvotes: 5
Reputation: 774
Try this way to force Angular to detect changes:
import { Component, OnInit, ViewContainerRef, ChangeDetectorRef, AfterContentChecked } from '@angular/core';
export class AppComponent {
constructor(private ref: ChangeDetectorRef){}
inputValue: number = 0;
handleChange($event: any) {
if ($event > 100) {
this.inputValue = 100;
}
this.ref.detectChanges()
}
}
Upvotes: 0
Reputation: 1517
The answer is pretty simple, this action is done when ngModel
changes, that's what (ngModelChange)
mean, the thing is that you're only taking input
value and assign it to inputValue
variable, but you're never listening to inputValue
changes.
Long story short, to solve your problem with minimal changes, use 2-way data binding in your .html
file to listen to your variable changes (In component class) like this:
[ngModel]
→ [(ngModel)]
<input type="number" [(ngModel)]="inputValue" (ngModelChange)="handleChange($event)"/>
Upvotes: 0