I'm learning Angular with TypeScript, and a few inconsistencies with JavaScript are troubling me. Here's a function which works perfectly with pure JavaScript (it programmatically sets min
and max
attributes to an input - please don't ask me why I'd want to do that programmatically):
function change_number(input,min,max) {
if(input.value > max)
input.value = max;
else if(input.value < min)
input.value = min;
}
<input type='number' oninput='change_number(this,1,5)' placeholder='Enter a number' />
In Angular and TypeScript, the function behaves strangely: If I enter 10
into the input field, it doesn't reset to 5
, until I enter another digit, meaning the value is only read after the input
event (and not on-input - don't know if that makes sense). What's more strange is it works fine with the keydown
and keyup
events. Why this behaviour? Is it linked to the way Angular binds events to inputs? I have a feeling understanding this would help me better understand Angular's binding mechanism with NgModel
FYI, here's how I called the function in Angular (using the Ionic Framework) - I used the bound quantity
in [(ngModel)]
as the value of the input:
<ion-input type='number' (input)='change_numbers(1,5)' [(ngModel)]='quantity'></ion-input>
The oninput event occurs when an element gets user input. This event occurs when the value of an <input> or <textarea> element is changed.
The NgModel class has the update property with an EventEmitter instance bound to it. This means we can't use (ngModelChange) without ngModel . Whereas the (change) event can be used anywhere, and I've demonstrated that above with the [value] property binding instead.
The oninput attribute fires when the value of an <input> or <textarea> element is changed. Tip: This event is similar to the onchange event. The difference is that the oninput event occurs immediately after the value of an element has changed, while onchange occurs when the element loses focus.
Unlike React, the browser fires onchange event after focus from input element is taken off. So when focus is set on an input element and something is typed, onchange won't be fired until and unless the input element is out of focus.
To understand why it behaves like that, first you need to be familiar with two basic concepts:
[(ngModel)]="quantity"
is nothing more than sugar syntax which is translated to [ngModel]="quantity" (ngModelChange)="quantity = $event"
With this knowledge, you can understand what really happens here:
stackblitz
onNgModelChange
is triggered, quantity
is set to 4
. The inner model value of input
(more specifically, ngModel
directive) is still equal to undefined
.change_numbers
is called, quantity
is not changed.ngModelChange
and change_numbers
all called in a single callstack due to how angular handles events). Change detection is performed. Because quantity
(4
now) is bound to the ngModel
(undefined
now), angular detects a need to update the UI (because undefined != 4
of course).8
.onNgModelChange
is triggered, quantity
is set to 48
. The inner model value of input
(more specifically, ngModel
directive) is still equal to 4
.change_numbers
is called, quantity
is changed to 5
.quantity
(5
now) is bound to the ngModel
(4
now), angular detects a need to update the UI (because 5 != 4
of course).7
.onNgModelChange
is triggered, quantity
is set to 57
. The inner model value of input
(more specifically, ngModel
directive) is still equal to 5
.change_numbers
is called, quantity
is changed to 5
.quantity
(5
now) is bound to the ngModel
(also 5
now), angular doesn't detect a need to update the UI (because 5 == 5
of course).A solution to that might be calling detectChanges
in onNgModelChange
method. It will work, because in this moment we will update the inner model
value of ngModel
(even if it is much higher than 5
). So even if we next decrease the value to 5
, angular still will detect a need to update the UI.
I hope that explains everything. If you'll have some questions, don't hesitate to let me know. Cheers!
Angular framework is behaving perfectly the way it has to. It is us who are a bit confused.
Angular handles forms in a bit different manner. There are two approaches to build forms in angular -
The Key difference between them is the above Sync/async behaviour. In simple terms we can say that.
x
<ion-input type='number' (input)='change_numbers(1,5)' [(ngModel)]='quantity'></ion-input>
Above one is a Template-driven approach. Here we are using [(ngModel)]
, this updates only after the data has changed.
In order to make a reactive input field, use below code :-
in app.component.ts file
import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
@Component({
selector: 'dd-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
form: FormGroup;
min = 0;
max = 5;
constructor(private fb: FormBuilder) {
this.buildForm();
}
buildForm(): void {
this.form = this.fb.group({
numberInput: ''
});
this.form.get('numberInput').valueChanges.pipe(distinctUntilChanged()).subscribe(_ => {
if (_) {
if (_ > this.max) {
this.form.get('numberInput').setValue(5);
}
if (_ < this.min) {
this.form.get('numberInput').setValue(0);
}
} else {
this.form.get('numberInput').reset('');
}
});
}
}
In app.html (Here i am posting 2 way to express it.)
<form [formGroup]="form">
<!-- <input type='number' [min]='0' [max]='5' formControlName="numberInput" /> we can also this one, more easy just set value dynamically -->
<input type='number' formControlName="numberInput" />
</form>
NOTE:- DONT FORGET TO IMPORT ReactiveFormsModule
More about forms can found in the official docs : -https://angular.io/guide/forms-overview
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With