N. younes
N. younes

Reputation: 101

Forcing input value (Angular)

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

Answers (4)

alex351
alex351

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. ;-)
    }
  }
}

Stackblitz

EDIT: removed the ms delay, like Chris suggested.

Upvotes: 2

Chris Hamilton
Chris Hamilton

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

Welyngton Dal Pra
Welyngton Dal Pra

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

Mohamed Karkotly
Mohamed Karkotly

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

Related Questions