html - ¿Por qué el evento oninput se comporta de manera diferente en Angular que en JavaScript?
Estoy aprendiendo Angular con TypeScript, y algunas inconsistencias con JavaScript me están molestando. Aquí hay una función que funciona perfectamente con JavaScript puro (programáticamente establece los atributos min y max en un input - por favor, no me preguntes por qué querría hacer eso programáticamente):
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' />En Angular y TypeScript, la función se comporta de manera extraña: si ingreso 10 en el campo de entrada, no se reinicia a 5 hasta que ingreso otro dígito, lo que significa que el valor solo se lee después del evento input (y no al introducir - no sé si eso tiene sentido). Lo más extraño es que funciona bien con los eventos keydown y keyup. ¿Por qué este comportamiento? ¿Está relacionado con la forma en que Angular vincula los eventos a los inputs? Tengo la sensación de que comprender esto me ayudaría a entender mejor el mecanismo de vinculación de Angular con NgModel.
Para que lo sepas, así es como llamé a la función en Angular (usando el Framework Ionic): usé el quantity vinculado en [(ngModel)] como el valor del input:
<ion-input type='number' (input)='change_numbers(1,5)' [(ngModel)]='quantity'></ion-input>
Respuesta #1
El framework Angular se está comportando perfectamente como debe. Somos nosotros quienes estamos un poco confundidos.
Angular maneja los formularios de una manera un poco diferente. Hay dos enfoques para construir formularios en Angular -
- Reactivo (Síncrono)
- Basado en plantillas (Asíncrono)
La diferencia clave entre ellos es el comportamiento síncrono/asíncrono mencionado arriba. En términos simples, podemos decir que.
- Síncrono - durante el cambio de datos.
- Asincrónico - después de que los datos han cambiado.
x
<ion-input type='number' (input)='change_numbers(1,5)' [(ngModel)]='quantity'></ion-input>
El anterior es un enfoque dirigido por plantillas. Aquí estamos usando [(ngModel)], esto se actualiza solo después de que los datos hayan cambiado.
Para crear un campo de entrada reactivo, utiliza el siguiente código :-
en el archivo app.component.ts
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('');
}
});
}
}
En app.html (Aquí estoy mostrando 2 formas de expresarlo.)
<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>
NOTA:- NO OLVIDES IMPORTAR ReactiveFormsModule
Más sobre formularios se puede encontrar en la documentación oficial: -https://angular.io/guide/forms-overview
Respuesta #2
Para entender por qué se comporta así, primero necesitas estar familiarizado con dos conceptos básicos:
- La sintaxis de "banana in the box"
[(ngModel)]="quantity"no es más que una sintaxis azucarada que se traduce a[ngModel]="quantity" (ngModelChange)="quantity=$event" - En Angular, la detección de cambios se activa por defecto al final de la pila de llamadas (esto es posible con zone.js).
Con este conocimiento, puedes entender lo que realmente sucede aquí:
stackblitz
- Escribes 4 en la entrada.
- Se dispara
onNgModelChange,quantityse establece en4. El valor interno del modelo deinput(más específicamente, la directivangModel) todavía es igual aundefined. - Cuando se llama a
change_numbers,quantityno se cambia. - Este es el fin de la pila de llamadas (nota que
ngModelChangeychange_numbersse llamaron todos en una sola pila de llamadas debido a cómo Angular maneja los eventos). Se realiza la detección de cambios. Debido a quequantity(4ahora) está vinculado angModel(undefinedahora), Angular detecta la necesidad de actualizar la interfaz de usuario (porqueundefined != 4, por supuesto). - La actualización se realizó, todo funciona como se esperaba.
- Tipeas el siguiente número -
8. - Se dispara
onNgModelChange,quantityse establece en48. El valor interno del modelo deinput(más específicamente, la directivangModel) todavía es igual a4. - Cuando se llama a
change_numbers,quantityse cambia a5. - Este es el final de la pila de llamadas. Se realiza la detección de cambios. Debido a que
quantity(5ahora) está vinculado angModel(4ahora), Angular detecta la necesidad de actualizar la interfaz de usuario (porque5 != 4, por supuesto). - La actualización se realizó, todo funciona como se esperaba.
- Tipeas el siguiente número -
7. - Se dispara
onNgModelChange,quantityse establece en57. El valor interno del modelo deinput(más específicamente, la directivangModel) todavía es igual a5. - Cuando se llama a
change_numbers,quantityse cambia a5. - Este es el final de la pila de llamadas. Se realiza la detección de cambios. Porque
quantity(5ahora) está vinculada alngModel(también5ahora), Angular no detecta la necesidad de actualizar la interfaz de usuario (porque5 == 5, por supuesto). - La actualización no se realiza, la interfaz de usuario permanece desactualizada.
Una solución a eso podría ser llamar a detectChanges en el método onNgModelChange. Funcionará, porque en ese momento actualizaremos el valor interno de model de ngModel (incluso si es mucho mayor que 5). Así que incluso si luego disminuimos el valor a 5, Angular todavía detectará la necesidad de actualizar la interfaz.
Espero que eso explique todo. Si tienes algunas preguntas, no dudes en hacérmelo saber. ¡Salud!
