简体   繁体   中英

Why does the oninput event behave differently in Angular than it does in JavaScript?

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>

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 -

  • Reactive (Synchronous)
  • Template-driven (Asynchronous)

The Key difference between them is the above Sync/async behaviour . In simple terms we can say that.

  • Synchronous - during the data change.
  • Asynchronous - after the data has changed.

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

To understand why it behaves like that, first you need to be familiar with two basic concepts:

  1. A "banana in the box" syntax [(ngModel)]="quantity" is nothing more than sugar syntax which is translated to [ngModel]="quantity" (ngModelChange)="quantity = $event"
  2. In angular, change detection is by default triggered at the end of the call stack (this is possible with zone.js).

With this knowledge, you can understand what really happens here:

stackblitz

  1. You type 4 into the input.
  2. onNgModelChange is triggered, quantity is set to 4 . The inner model value of input (more specifically, ngModel directive) is still equal to undefined .
  3. change_numbers is called, quantity is not changed.
  4. This is the end of the callstack (note that 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).
  5. Update is performed, everything works as expected.

  1. You type the next number - 8 .
  2. onNgModelChange is triggered, quantity is set to 48 . The inner model value of input (more specifically, ngModel directive) is still equal to 4 .
  3. change_numbers is called, quantity is changed to 5 .
  4. This is the end of the callstack. Change detection is performed. Because quantity ( 5 now) is bound to the ngModel ( 4 now), angular detects a need to update the UI (because 5 != 4 of course).
  5. Update is performed, everything works as expected.

  1. You type the next number - 7 .
  2. onNgModelChange is triggered, quantity is set to 57 . The inner model value of input (more specifically, ngModel directive) is still equal to 5 .
  3. change_numbers is called, quantity is changed to 5 .
  4. This is the end of the callstack. Change detection is performed. Because 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).
  5. Update is not performed, UI remains stale.

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!

Try this out, this works well, let me know if this is not what you want to achieve

stackblitz

html

<input type='number' (change)='change_numbers(1,5)' [(ngModel)]="quantity">

javascript

  change_numbers(min,max) {
   if(this.quantity > max)
     this.quantity = max;
   else if(this.quantity < min)
     this.quantity= min;
 }

Not sure if this helps, but I found these;

how to use oninput in Angular?

oninput not working in Angular. Alternative?

They seem to talk a lot about using (input) ; if you've tried it?

Reading on google about this issue, it just seems like if you're using Angular you have to do things " The Angular Way " or oddities like what you're trying to seek an answer for occur.

As Kuiil from The Mandolorian says; "This is the way"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM