Cómo observar los cambios de forma en Angular

151

En Angular, podría tener una forma similar a esta:

<ng-form>
    <label>First Name</label>
    <input type="text" ng-model="model.first_name">

    <label>Last Name</label>
    <input type="text" ng-model="model.last_name">
</ng-form>

Dentro del controlador correspondiente, podría observar fácilmente los cambios en el contenido de ese formulario de la siguiente manera:

function($scope) {

    $scope.model = {};

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

}

Aquí hay un ejemplo angular en JSFiddle .

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

Aquí hay un ejemplo angular en Plunker .

tambor
fuente
en lugar de ver algunos datos de su controlador, creo que debería activar un evento (como el antiguo ng-change) desde su formulario.
Deblaton Jean-Philippe
por cierto, la razón para eliminar el alcance es deshacerse de esos observadores. No creo que haya un reloj escondido en algún lugar en angular 2
Deblaton Jean-Philippe
44
Si te entiendo correctamente, ¿estás sugiriendo que agregue un (ngModelChange)="onModelChange($event)"atributo a cada entrada de formulario para lograr esto?
tambler
1
¿La instancia del formulario en sí no emite algún tipo de evento de cambio? Si es así, ¿cómo se accede?
tambler
Si estuviera seguro de qué hacer, habría respondido en lugar de comentar. Todavía no he usado angular 2.0, pero por lo que he leído, el reloj ha desaparecido por completo para tener un marco basado en eventos (en lugar del reloj profundo realizado en cada resumen)
Deblaton Jean-Philippe

Respuestas:

189

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 una valueChangespropiedad 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
    })
  }
}

Echa un vistazo valueChangesen acción en esta demostración : http://plnkr.co/edit/xOz5xaQyMlRzSrgtt7Wn?p=preview

dfsq
fuente
2
Esto está muy cerca de la marca. Para confirmar - me estás diciendo que es no posible suscribirse a una forma de valueChangesemisor de sucesos si esa forma se define únicamente dentro de la plantilla? En otras palabras, dentro del constructor de un componente, ¿no es posible obtener una referencia a un formulario que se definió únicamente dentro de la plantilla de ese componente y no con la ayuda de FormBuilder?
tambler
Esta es la respuesta correcta. Si no tiene un generador de formularios, está haciendo formularios basados ​​en plantillas. Probablemente haya una forma de inyectar el formulario, pero si desea el formulario Observable.valueChanges definitivamente debe usar formBuilder y abandonar ng-model
Angular University
2
@tambler, puede obtener una referencia a NgForm usando @ViewChild(). Ver mi respuesta actualizada.
Mark Rajcok
1
¿No necesitas darte de baja en destruir?
Bazinga
1
@galvan No creo que pueda haber una fuga. El formulario es parte del componente y se eliminará correctamente en destruir con todos sus campos y oyentes de eventos.
dfsq
107

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

Si no está utilizando FormBuilder, hay dos formas de ser notificado de los cambios.

Método 1

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

<input type="text" class="form-control" required [ngModel]="model.first_name"
         (ngModelChange)="doSomething($event)">

Luego en su componente:

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

La página Formularios tiene información adicional sobre ngModel que es relevante aquí:

El ngModelChangeno es un <input>evento de elemento. En realidad es una propiedad de evento de la NgModeldirectiva. Cuando Angular ve un objetivo vinculante en el formulario [(x)], espera que la xdirectiva tenga una xpropiedad de entrada y una xChangepropiedad de salida.

La otra peculiaridad es la expresión de plantilla, model.name = $event. Estamos acostumbrados a ver un $eventobjeto proveniente de un evento DOM. La propiedad ngModelChange no produce un evento DOM; es una EventEmitterpropiedad angular 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 del evento, como eliminar el rebote o acelerar las teclas.

En su caso, supongo que quiere hacer algo especial.

Método 2

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

<form #myForm="ngForm" (ngSubmit)="onSubmit()">
  ....
  <input type="text" ngControl="firstName" class="form-control" 
   required [(ngModel)]="model.first_name">
  ...
  <input type="text" ngControl="lastName" class="form-control" 
   required [(ngModel)]="model.last_name">

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 lo valueChangesobservable (como eliminar el rebote / esperar un poco antes de procesar los cambios).

Mark Rajcok
fuente
61

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

Los observables permiten no solo usar el subscribemétodo (algo similar al thenmé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 a este nivel el tiempo de rebote con el debounceTimemétodo. 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 activar (algunos asincrónicos, 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 switchMapmétodo:

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

Incluso va más allá al vincular 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 asynctubería:

<ul>
  <li *ngFor="#elt of (list | async)">{{elt.name}}</li>
</ul>

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

Espero que te ayude, Thierry

Thierry Templier
fuente
La propiedad 'valueChanges' no existe en el tipo 'string'
Toolkit
Me he atascado, en el archivo TS, ¿dónde se debe colocar el método de cambio de formulario de verificación? Si lo almacenamos en ngAfterViewInit () {}, parece que this.form.valueChanges siempre llama si necesitábamos implementar algunas cadenas de procesamiento para datos actualizados en formularios.
Tài Nguyễn
1

Ampliando las sugerencias de Mark ...

Método 3

Implemente la detección de cambios "profundos" en el modelo. Las ventajas implican principalmente evitar la incorporación de 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 un trabajo adicional para implementar cosas tales como eliminar el rebote según lo sugerido por Thierry, y esto también detectará sus propios cambios programáticos, así que úselo 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];
      }
    }
  }

Probar en Plunker

N8allan
fuente
1

Para 5+versión angular . Poner la versión ayuda ya que 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)
        })
}
Krishnadas PC
fuente
0

Pensé en usar el método (ngModelChange), luego pensé en el método FormBuilder y finalmente decidí una variación del Método 3. Esto ahorra la decoración de la plantilla con atributos adicionales y recoge automáticamente los cambios en el 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 un 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));
    }
}
Ulfius
fuente