¿Cómo observar los cambios de forma en AngularJS?

En AngularJS, podría tener una forma que se parece a esto:

      

Dentro del controlador correspondiente, podría mirar fácilmente los cambios a los contenidos de ese formulario, así:

 function($scope) { $scope.model = {}; $scope.$watch('model', () => { // Model has updated }, true); } 

Aquí hay un ejemplo de AngularJS en JSFiddle .

Tengo problemas para descubrir cómo lograr lo mismo en Angular. Obviamente, ya no tenemos $scope , etc., pero seguramente hay un método por el cual se puede lograr lo mismo.

Aquí hay un ejemplo angular en Plunker .

UPD. La respuesta y la demostración se actualizan para alinearse con la última versión angular.


Puede suscribirse a cambios de formulario completos debido al hecho de que FormGroup que representa un formulario proporciona la propiedad valueChanges que es una instancia observable:

 this.form.valueChanges.subscribe(data => console.log('Form changes', data)); 

En este caso, necesitaría construir el formulario manualmente usando FormBuilder . Algo como esto:

 export class App { constructor(private formBuilder: FormBuilder) { this.form = formBuilder.group({ firstName: 'Thomas', lastName: 'Mann' }) this.form.valueChanges.subscribe(data => { console.log('Form changes', data) this.output = data }) } } 

Verifique los valueChanges de valueChanges en acción en esta demostración : http://plnkr.co/edit/xOz5xaQyMlRzSrgtt7Wn?p=preview

Si está utilizando FormBuilder , consulte la respuesta de @ dfsq.

Si no está utilizando FormBuilder , hay dos maneras de recibir notificaciones de cambios.

Método 1

Como se discutió en los comentarios sobre la pregunta, use un enlace de evento en cada elemento de entrada. Agregar a su plantilla:

  

Luego en tu componente:

 doSomething(newValue) { model.first_name = newValue; console.log(newValue) } 

La página de Formularios contiene información adicional acerca de ngModel que es relevante aquí:

ngModelChange no es un evento de elemento . En realidad, es un evento propiedad de la directiva NgModel . Cuando Angular ve un objective vinculante en la forma [(x)] , espera que la directiva x tenga una propiedad de entrada x y una propiedad de salida xChange .

La otra rareza es la expresión de la plantilla, model.name = $event . Estamos acostumbrados a ver un objeto $event proveniente de un evento DOM. La propiedad ngModelChange no produce un evento DOM; es una propiedad Angular EventEmitter que devuelve el valor del cuadro de entrada cuando se dispara ..

Casi siempre preferimos [(ngModel)] . Podríamos dividir el enlace si tuviéramos que hacer algo especial en el manejo de eventos como rebotar o estrangular los golpes de tecla.

En tu caso, supongo que quieres hacer algo especial.

Método 2

Defina una variable de plantilla local y ngForm en ngForm .
Use ngControl en los elementos de entrada.
Obtenga una referencia a la directiva NgForm del formulario usando @ViewChild, luego suscríbase al Grupo de control de NgForm para ver los cambios:

 
.... ... class MyForm { @ViewChild('myForm') form; ... ngAfterViewInit() { console.log(this.form) this.form.control.valueChanges .subscribe(values => this.doSomething(values)); } doSomething(values) { console.log(values); } }

plunker

Para obtener más información sobre el Método 2, vea el video de Savkin .

Consulte también la respuesta de @ Thierry para obtener más información sobre lo que puede hacer con los valueChanges observables (como el valueChanges / esperar un poco antes de procesar los cambios).

Para completar un poco más de excelentes respuestas anteriores, debe tener en cuenta que los formularios aprovechan los observables para detectar y manejar cambios de valores. Es algo realmente importante y poderoso. Tanto Mark como dfsq describieron este aspecto en sus respuestas.

Los observables permiten no solo utilizar el método de subscribe (algo similar al método de promesas en Angular 1). Puede ir más allá si es necesario para implementar algunas cadenas de procesamiento de datos actualizados en formularios.

Quiero decir que puede especificar en este nivel el tiempo de eliminación de rebote con el método debounceTime . Esto le permite esperar una cantidad de tiempo antes de manejar el cambio y manejar correctamente varias entradas:

 this.form.valueChanges .debounceTime(500) .subscribe(data => console.log('form changes', data)); 

También puede conectar directamente el procesamiento que desea desencadenar (algunos asíncronos, por ejemplo) cuando se actualizan los valores. Por ejemplo, si desea manejar un valor de texto para filtrar una lista basada en una solicitud AJAX, puede aprovechar el método switchMap :

 this.textValue.valueChanges .debounceTime(500) .switchMap(data => this.httpService.getListValues(data)) .subscribe(data => console.log('new list values', data)); 

Incluso puede ir más allá vinculando el observable devuelto directamente a una propiedad de su componente:

 this.list = this.textValue.valueChanges .debounceTime(500) .switchMap(data => this.httpService.getListValues(data)) .subscribe(data => console.log('new list values', data)); 

y mostrarlo usando la tubería async :

 
  • {{elt.name}}

Solo para decir que necesitas pensar la forma de manejar las formas de manera diferente en Angular2 (una forma mucho más poderosa ;-)).

Espero que te ayude, Thierry

Ampliando las sugerencias de Mark …

Método 3

Implementar la detección de cambios “profundos” en el modelo. Las ventajas consisten principalmente en evitar incorporar aspectos de la interfaz de usuario en el componente; esto también capta los cambios programáticos realizados en el modelo. Dicho esto, requeriría trabajo adicional implementar cosas como el antirrebote según lo sugerido por Thierry, y esto también detectará sus propios cambios programáticos, por lo tanto, use con precaución.

 export class App implements DoCheck { person = { first: "Sally", last: "Jones" }; oldPerson = { ...this.person }; // ES6 shallow clone. Use lodash or something for deep cloning ngDoCheck() { // Simple shallow property comparison - use fancy recursive deep comparison for more complex needs for (let prop in this.person) { if (this.oldPerson[prop] !== this.person[prop]) { console.log(`person.${prop} changed: ${this.person[prop]}`); this.oldPerson[prop] = this.person[prop]; } } } 

Pruebe en Plunker

Pensé en usar el método (ngModelChange), luego pensé en el método FormBuilder y finalmente decidí en una variante del Método 3. Esto ahorra la decoración de la plantilla con atributos adicionales y recoge automáticamente los cambios al modelo, reduciendo la posibilidad de olvidar algo. con el Método 1 o 2.

Simplificando el Método 3 un poco …

 oldPerson = JSON.parse(JSON.stringify(this.person)); ngDoCheck(): void { if (JSON.stringify(this.person) !== JSON.stringify(this.oldPerson)) { this.doSomething(); this.oldPerson = JSON.parse(JSON.stringify(this.person)); } } 

Puede agregar un tiempo de espera para llamar solo a DoSomething () después de x número de milisegundos para simular rebote.

 oldPerson = JSON.parse(JSON.stringify(this.person)); ngDoCheck(): void { if (JSON.stringify(this.person) !== JSON.stringify(this.oldPerson)) { if (timeOut) clearTimeout(timeOut); let timeOut = setTimeout(this.doSomething(), 2000); this.oldPerson = JSON.parse(JSON.stringify(this.person)); } } 

Para la versión angular 5+ . Poner la versión ayuda como angular hace muchos cambios.

 ngOnInit() { this.myForm = formBuilder.group({ firstName: 'Thomas', lastName: 'Mann' }) this.formControlValueChanged() // Note if you are doing an edit/fetching data from an observer this must be called only after your form is properly initialized otherwise you will get error. } formControlValueChanged(): void { this.myForm.valueChanges.subscribe(value => { console.log('value changed', value) }) }