Hoe te letten op vormveranderingen in Angular

In Angular kan ik een formulier hebben dat er als volgt uitziet:

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

Binnen de bijbehorende controller kan ik gemakkelijk kijken naar wijzigingen in de inhoud van dat formulier, zoals:

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

Hier is een Hoekig voorbeeld op JSFiddle.

Ik heb moeite om uit te zoeken hoe ik hetzelfde kan bereiken in Angular. Het is duidelijk dat we niet langer $scope, $rootScope hebben. Er is toch zeker een methode waarmee hetzelfde kan worden bereikt?

Hier is een Hoekig voorbeeld op Plunker.


Antwoord 1, autoriteit 100%

UPD.Het antwoord en de demo zijn bijgewerkt om af te stemmen op de nieuwste Angular.


Je kunt je abonneren op volledige formulierwijzigingen vanwege het feit dat FormGroupdie een formulier vertegenwoordigt, levert de eigenschap valueChangeswat een waarneembare instantie is:

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

In dit geval moet u het formulier handmatig samenstellen met FormBuilder. Zoiets als dit:

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

Bekijk valueChangesin actie in deze demo: http://plnkr.co/edit/xOz5xaQyMlRzSrgtt7Wn?p=preview


Antwoord 2, autoriteit 54%

Als je FormBuildergebruikt, zie dan het antwoord van @dfsq.

Als u FormBuilderniet gebruikt, zijn er twee manieren om op de hoogte te worden gehouden van wijzigingen.

Methode 1

Zoals besproken in de opmerkingen over de vraag, gebruik je een gebeurtenisbindingop elk invoerelement. Voeg toe aan uw sjabloon:

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

Vervolgens in uw component:

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

De pagina Formulierenbevat aanvullende informatie over ngModel die relevant is hier:

De ngModelChangeis geen <input>elementgebeurtenis. Het is eigenlijk een gebeurteniseigenschap van de NgModel-richtlijn. Wanneer Angular een bindend doel ziet in de vorm [(x)], verwacht het dat de x-instructie een xinvoereigenschap en een xChangeuitvoereigenschap.

De andere eigenaardigheid is de sjabloonuitdrukking, model.name = $event. We zijn gewend dat een $event-object afkomstig is van een DOM-gebeurtenis. De eigenschap ngModelChange produceert geen DOM-gebeurtenis; het is een Angular EventEmitter-eigenschap die de waarde van het invoervak ​​retourneert wanneer deze wordt geactiveerd..

We geven bijna altijd de voorkeur aan [(ngModel)]. We zouden de binding kunnen splitsen als we iets speciaals moesten doen bij het afhandelen van het evenement, zoals debounce of het vertragen van de toetsaanslagen.

In jouw geval, neem ik aan dat je iets speciaals wilt doen.

Methode 2

Definieer een lokale sjabloonvariabele en stel deze in op ngForm.

Gebruik ngControl op de invoerelementen.

Krijg een verwijzing naar de NgForm-richtlijn van het formulier met @ViewChild, en abonneer je op de NgForm’s ControlGroup voor wijzigingen:

<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

Voor meer informatie over methode 2, zie Savkin’s video.

Zie ook het antwoord van @Thierry voor meer informatie over wat u kunt doen met de waarneembare valueChanges(zoals debouncing/een beetje wachten voordat wijzigingen worden verwerkt).


Antwoord 3, autoriteit 31%

Om wat meer eerdere geweldige antwoorden te voltooien, moet u zich ervan bewust zijn dat formulieren gebruik maken van waarneembare waarden om waardeveranderingen te detecteren en af ​​te handelen. Het is iets heel belangrijks en krachtigs. Zowel Mark als dfsq beschreven dit aspect in hun antwoorden.

Observables laten niet alleen toe om de subscribemethode te gebruiken (iets vergelijkbaar met de thenmethode van beloften in Angular 1). U kunt indien nodig verder gaan om enkele verwerkingsketens voor bijgewerkte gegevens in formulieren te implementeren.

Ik bedoel dat je op dit niveau de debounce-tijd kunt specificeren met de debounceTime-methode. Hierdoor kunt u enige tijd wachten voordat u de wijziging verwerkt en verschillende invoer correct afhandelt:

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

U kunt de verwerking die u wilt activeren ook rechtstreeks aansluiten (bijvoorbeeld een asynchrone) wanneer waarden worden bijgewerkt. Als u bijvoorbeeld een tekstwaarde wilt verwerken om een ​​lijst te filteren op basis van een AJAX-verzoek, kunt u gebruikmaken van de switchMap-methode:

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

U gaat zelfs verder door het geretourneerde waarneembare direct te koppelen aan een eigenschap van uw component:

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

en geef het weer met behulp van de async-pipe:

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

Om maar te zeggen dat je een andere manier moet bedenken om met formulieren om te gaan in Angular2 (een veel krachtigere manier ;-)).

Ik hoop dat het je helpt,
Thierry


Antwoord 4

Voor hoekige 5+versie. Het plaatsen van een versie helpt omdat de hoek veel veranderingen aanbrengt.

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

Antwoord 5

Uitbreiden op de suggesties van Mark…

Methode 3

Implementeer “diepe” wijzigingsdetectie op het model. De voordelen hebben voornamelijk betrekking op het vermijden van het opnemen van aspecten van de gebruikersinterface in de component; dit vangt ook programmatische wijzigingen aan het model op. Dat gezegd hebbende, zou het extra werk vergen om zaken als debouncing te implementeren, zoals voorgesteld door Thierry, en dit zal ook je eigenprogrammatische wijzigingen opvangen, dus wees voorzichtig.

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

Probeer Plunker


Antwoord 6

Ik dacht aan het gebruik van de (ngModelChange) methode, dacht toen aan de FormBuilder methode, en kwam uiteindelijk uit op een variant van methode 3. Dit bespaart het verfraaien van de sjabloon met extra attributen en pikt automatisch wijzigingen in het model op – waardoor de mogelijkheid kleiner wordt iets vergeten met methode 1 of 2.

Methode 3 een beetje vereenvoudigen…

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

Je zou een time-out kunnen toevoegen om doSomething() pas na x aantal milliseconden aan te roepen om debounce te simuleren.

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

Other episodes