Come controllare i cambiamenti di forma in Angolare


151

In Angular, potrei avere una forma simile a questa:

<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>

All'interno del controller corrispondente, potrei facilmente controllare le modifiche ai contenuti di quel modulo in questo modo:

function($scope) {

    $scope.model = {};

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

}

Ecco un esempio angolare su JSFiddle .

Ho difficoltà a capire come realizzare la stessa cosa in Angular. Ovviamente, non abbiamo più $scope$ rootScope. Sicuramente esiste un metodo con cui si può realizzare la stessa cosa?

Ecco un esempio angolare su Plunker .


invece di guardare alcuni dati dal tuo controller, penso che dovresti lanciare un evento (come il vecchio ng-change) dal tuo modulo.
Deblaton Jean-Philippe,

a proposito, il motivo per cui si rimuove l'ambito è quello di sbarazzarsi di quegli osservatori. Non credo che ci sia un orologio nascosto da qualche parte in
Angular

4
Se ti sto capendo correttamente, stai suggerendo di aggiungere un (ngModelChange)="onModelChange($event)"attributo a ogni input del modulo per ottenere questo risultato?
tambler

1
L'istanza del modulo stesso non emette alcun tipo di evento di modifica? In tal caso, come si accede?
tambler

Se fossi sicuro di cosa fare, avrei risposto invece di commentare. Non ho ancora usato Angular 2.0, ma da quello che ho letto, l'orologio è completamente scomparso per avere un framework basato sugli eventi (invece del deep watch eseguito ad ogni digest)
Deblaton Jean-Philippe

Risposte:


189

UPD. La risposta e la demo vengono aggiornate per allinearle all'ultima angolare.


È possibile abbonarsi alle modifiche dell'intero modulo a causa del fatto che FormGroup che rappresenta un modulo fornisce valueChangesproprietà che è un'istanza osservabile :

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

In questo caso dovrai costruire il modulo manualmente usando FormBuilder . Qualcosa come questo:

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

Guarda valueChangesin azione in questa demo : http://plnkr.co/edit/xOz5xaQyMlRzSrgtt7Wn?p=preview


2
Questo è molto vicino al segno. Per confermare - mi stai dicendo che è non è possibile sottoscrivere un modulo valueChangesemettitore evento se quella forma è definito esclusivamente all'interno del modello? In altre parole: all'interno del costruttore di un componente, non è possibile ottenere un riferimento a un modulo definito esclusivamente all'interno del modello di quel componente e non con l'aiuto di FormBuilder?
tambler

Questa è la risposta esatta. Se non disponi di un generatore di moduli, stai realizzando moduli basati su modelli. c'è probabilmente un modo per ottenere ancora il modulo iniettato, ma se vuoi l'Objectable form.valueChanges dovresti usare definitivamente formBuilder e abbandonare ng-model
Angular University,

2
@tambler, puoi ottenere un riferimento a NgForm usando @ViewChild(). Vedi la mia risposta aggiornata.
Mark Rajcok,

1
Non è necessario annullare l'iscrizione per distruggere?
Bazinga,

1
@galvan Non credo che ci possa essere una perdita. Il modulo fa parte del componente e verrà correttamente disposto in modo da distruggere con tutti i suoi campi e listener di eventi.
dfsq,

107

Se stai usando FormBuilder, vedi la risposta di @ dfsq.

Se non si utilizza FormBuilder, ci sono due modi per essere avvisati delle modifiche.

Metodo 1

Come discusso nei commenti sulla domanda, utilizzare un'associazione di eventi su ciascun elemento di input. Aggiungi al tuo modello:

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

Quindi nel tuo componente:

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

La pagina dei moduli contiene alcune informazioni aggiuntive su ngModel rilevanti qui:

L' evento ngModelChangenon è un <input>elemento. In realtà è una proprietà dell'evento della NgModeldirettiva. Quando Angular vede un target vincolante nel modulo [(x)], si aspetta che la xdirettiva abbia una xproprietà di input e una xChangeproprietà di output.

L'altra stranezza è l'espressione modello, model.name = $event. Siamo abituati a vedere un $eventoggetto proveniente da un evento DOM. La proprietà ngModelChange non produce un evento DOM; è una EventEmitterproprietà Angolare che restituisce il valore della casella di input quando viene attivato.

Preferiamo quasi sempre [(ngModel)]. Potremmo dividere l'associazione se dovessimo fare qualcosa di speciale nella gestione degli eventi come rimbalzare o limitare i tasti.

Nel tuo caso, suppongo che tu voglia fare qualcosa di speciale.

Metodo 2

Definire una variabile di modello locale e impostarla su ngForm.
Usa ngControl sugli elementi di input.
Ottieni un riferimento alla direttiva NgForm del modulo utilizzando @ViewChild, quindi iscriviti al gruppo di controllo di NgForm per le modifiche:

<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

Per ulteriori informazioni sul metodo 2, vedere il video di Savkin .

Vedi anche la risposta di @ Thierry per maggiori informazioni su cosa puoi fare con l' valueChangesosservabile (come rimbalzare / aspettare un po 'prima di elaborare le modifiche).


61

Per completare altre ottime risposte precedenti, devi essere consapevole che i moduli sfruttano gli osservabili per rilevare e gestire le variazioni di valore. È qualcosa di veramente importante e potente. Sia Mark che dfsq hanno descritto questo aspetto nelle loro risposte.

Gli osservabili consentono non solo di utilizzare il subscribemetodo (qualcosa di simile al thenmetodo delle promesse in Angular 1). Se necessario, è possibile andare oltre per implementare alcune catene di elaborazione per i dati aggiornati nei moduli.

Voglio dire che puoi specificare a questo livello il tempo di rimbalzo con il debounceTimemetodo. Ciò consente di attendere un periodo di tempo prima di gestire la modifica e gestire correttamente diversi input:

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

È inoltre possibile collegare direttamente l'elaborazione che si desidera attivare (alcune asincrone per esempio) quando i valori vengono aggiornati. Ad esempio, se si desidera gestire un valore di testo per filtrare un elenco in base a una richiesta AJAX, è possibile sfruttare il switchMapmetodo:

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

Puoi anche andare oltre collegando l'osservabile restituito direttamente a una proprietà del tuo componente:

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

e visualizzalo usando la asyncpipe:

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

Solo per dire che è necessario pensare al modo di gestire le forme in modo diverso in Angular2 (un modo molto più potente ;-)).

Spero che ti aiuti, Thierry


La proprietà 'valueChanges' non esiste sul tipo 'string'
Toolkit

Ho bloccato, nel file TS, dove dovrebbe essere collocato il metodo di modifica del modulo di controllo? Se lo memorizziamo in ngAfterViewInit () {}, sembra che this.form.valueChanges chiami sempre se dovevamo implementare alcune catene di elaborazione per i dati aggiornati nei moduli.
Tài Nguyễn,

1

Espansione dei suggerimenti di Mark ...

Metodo 3

Implementare il rilevamento di modifiche "profonde" sul modello. I vantaggi riguardano principalmente l'evitare l'incorporazione di aspetti dell'interfaccia utente nel componente; questo rileva anche le modifiche programmatiche apportate al modello. Detto questo, richiederebbe un lavoro extra per implementare cose come il rimbalzo come suggerito da Thierry, e questo colpirà anche le tue modifiche programmatiche, quindi usalo con cautela.

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];
      }
    }
  }

Prova in Plunker


1

Per 5+versione angolare . Mettere la versione aiuta come angolare apporta molte modifiche.

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

0

Ho pensato di utilizzare il metodo (ngModelChange), poi ho pensato al metodo FormBuilder e alla fine ho optato per una variante del metodo 3. Ciò consente di salvare la decorazione del modello con attributi extra e di raccogliere automaticamente le modifiche al modello, riducendo la possibilità di dimenticare qualcosa con il metodo 1 o 2.

Semplificare un po 'il Metodo 3 ...

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));
    }
}

È possibile aggiungere un timeout per chiamare doSomething () solo dopo un numero x di millisecondi per simulare il rimbalzo.

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));
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.