So achten Sie in Angular auf Formänderungen

151

In Angular könnte ich eine Form haben, die so aussieht:

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

Innerhalb des entsprechenden Controllers konnte ich leicht nach Änderungen am Inhalt dieses Formulars suchen:

function($scope) {

    $scope.model = {};

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

}

Hier ist ein Angular-Beispiel für JSFiddle .

Ich habe Probleme herauszufinden, wie ich dasselbe in Angular erreichen kann. Offensichtlich haben wir $scope$ rootScope nicht mehr . Sicher gibt es eine Methode, mit der das Gleiche erreicht werden kann?

Hier ist ein Angular-Beispiel für Plunker .

Tambler
quelle
Anstatt einige Daten von Ihrem Controller zu sehen, sollten Sie ein Ereignis (wie die alte ng-Änderung) aus Ihrem Formular auslösen.
Deblaton Jean-Philippe
Übrigens ist der Grund für das Entfernen des Oszilloskops, diese Beobachter loszuwerden. Ich glaube nicht, dass irgendwo in Winkel 2 eine Uhr versteckt ist
Deblaton Jean-Philippe
4
Wenn ich Sie richtig verstehe, schlagen Sie vor, dass ich jeder Formulareingabe ein (ngModelChange)="onModelChange($event)"Attribut hinzufüge , um dies zu erreichen?
Tambler
1
Gibt die Formularinstanz selbst kein Änderungsereignis aus? Wenn ja, wie greifen Sie darauf zu?
Tambler
Wenn ich mir sicher wäre, was ich tun soll, hätte ich geantwortet, anstatt zu kommentieren. Ich habe Angular 2.0 noch nicht verwendet, aber nach dem, was ich gelesen habe, ist die Uhr vollständig verschwunden, um ein ereignisbasiertes Framework zu haben (anstelle der bei jedem Digest durchgeführten Deep Watch)
Deblaton Jean-Philippe

Antworten:

189

UPD. Die Antwort und die Demo werden aktualisiert, um sie an das neueste Angular anzupassen.


Sie können ganze Formularänderungen abonnieren, da FormGroup, die ein Formular darstellt, eine valueChangesEigenschaft bereitstellt, die eine beobachtbare Instanz ist:

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

In diesem Fall müssten Sie das Formular manuell mit FormBuilder erstellen . Etwas wie das:

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

Schauen Sie sich valueChangesdiese Demo in Aktion an : http://plnkr.co/edit/xOz5xaQyMlRzSrgtt7Wn?p=preview

dfsq
quelle
2
Dies ist sehr nah an der Marke. Zur Bestätigung: Sagen Sie mir, dass es nicht möglich ist, den valueChangesEreignisemitter eines Formulars zu abonnieren, wenn dieses Formular ausschließlich in der Vorlage definiert ist? Mit anderen Worten: Ist es im Konstruktor einer Komponente nicht möglich, einen Verweis auf ein Formular abzurufen, das ausschließlich in der Vorlage dieser Komponente und nicht mithilfe von FormBuilder definiert wurde?
Tambler
Das ist die richtige Antwort. Wenn Sie keinen Form Builder haben, erstellen Sie vorlagengesteuerte Formulare. Es gibt wahrscheinlich eine Möglichkeit, das Formular noch zu injizieren, aber wenn Sie das Observable form.valueChanges möchten, sollten Sie formBuilder definitiv verwenden und ng-model
Angular University am
2
@tambler, Sie können einen Verweis auf NgForm mit erhalten @ViewChild(). Siehe meine aktualisierte Antwort.
Mark Rajcok
1
Sie müssen sich bei destroy nicht abmelden?
Bazinga
1
@galvan Ich glaube nicht, dass es ein Leck geben kann. Das Formular ist Teil der Komponente und wird bei Zerstörung mit all seinen Feldern und Ereignis-Listenern ordnungsgemäß entsorgt.
dfsq
107

Wenn Sie verwenden FormBuilder , lesen Sie die Antwort von @ dfsq.

Wenn Sie nicht verwenden FormBuilder , gibt es zwei Möglichkeiten, um über Änderungen informiert zu werden.

Methode 1

Verwenden Sie, wie in den Kommentaren zur Frage erläutert, eine Ereignisbindung für jedes Eingabeelement. Zu Ihrer Vorlage hinzufügen:

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

Dann in Ihrer Komponente:

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

Auf der Seite Formulare finden Sie einige zusätzliche Informationen zu ngModel, die hier relevant sind:

Das ngModelChangeist kein <input>Elementereignis. Es ist eigentlich eine Ereigniseigenschaft der NgModelDirektive. Wenn Angular ein Bindungsziel im Formular sieht [(x)], erwartet es, dass die xDirektive eine xEingabeeigenschaft und eine hatxChange Ausgabeeigenschaft hat.

Die andere Kuriosität ist der Vorlagenausdruck model.name = $event. Wir sind es gewohnt, ein $eventObjekt zu sehen, das von einem DOM-Ereignis stammt. Die Eigenschaft ngModelChange erzeugt kein DOM-Ereignis. Es ist ein WinkelEventEmitter Eigenschaft, die beim Auslösen den Eingabefeldwert zurückgibt.

Wir bevorzugen fast immer [(ngModel)]. Wir könnten die Bindung aufteilen, wenn wir bei der Ereignisbehandlung etwas Besonderes tun müssten, z. B. das Entprellen oder Drosseln der Tastenanschläge.

In Ihrem Fall möchten Sie wahrscheinlich etwas Besonderes tun.

Methode 2

Definieren Sie eine lokale Vorlagenvariable und setzen Sie sie auf ngForm.
Verwenden Sie ngControl für die Eingabeelemente.
Rufen Sie mit @ViewChild einen Verweis auf die NgForm-Direktive des Formulars ab und abonnieren Sie die ControlGroup der NgForm, um Änderungen vorzunehmen:

<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

Weitere Informationen zu Methode 2 finden Sie in Savkins Video .

Siehe auch die Antwort von @ Thierry für weitere Informationen darüber, was Sie mit dem valueChangesObservable tun können (z. B. Entprellen / Warten vor dem Verarbeiten von Änderungen).

Mark Rajcok
quelle
61

Um ein bisschen mehr frühere großartige Antworten zu vervollständigen, müssen Sie sich bewusst sein, dass Formulare Observable nutzen, um Wertänderungen zu erkennen und zu verarbeiten. Es ist etwas wirklich Wichtiges und Mächtiges. Sowohl Mark als auch dfsq haben diesen Aspekt in ihren Antworten beschrieben.

Mit Observablen kann nicht nur die subscribeMethode verwendet werden (ähnlich thender Versprechensmethode in Winkel 1). Bei Bedarf können Sie einige Verarbeitungsketten für aktualisierte Daten in Formularen implementieren.

Ich meine, Sie können auf dieser Ebene die Entprellzeit mit der debounceTimeMethode angeben . Auf diese Weise können Sie einige Zeit warten, bevor Sie die Änderung vornehmen, und mehrere Eingaben korrekt verarbeiten:

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

Sie können die Verarbeitung, die Sie auslösen möchten (z. B. eine asynchrone), auch direkt anschließen, wenn Werte aktualisiert werden. Wenn Sie beispielsweise einen Textwert verarbeiten möchten, um eine Liste basierend auf einer AJAX-Anforderung zu filtern, können Sie die folgende switchMapMethode nutzen:

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

Sie gehen sogar noch weiter, indem Sie das zurückgegebene Observable direkt mit einer Eigenschaft Ihrer Komponente verknüpfen:

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

und zeigen Sie es mit der async Pipe an:

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

Um nur zu sagen, dass Sie überlegen müssen, wie Sie mit Formularen in Angular2 anders umgehen können (eine viel leistungsfähigere Methode ;-)).

Hoffe es hilft dir, Thierry

Thierry Templier
quelle
Die Eigenschaft 'valueChanges' ist für den Typ 'string' nicht vorhanden
Toolkit
Ich habe in der TS-Datei festgehalten, wo die Methode zum Ändern des Prüfformulars platziert werden soll. Wenn wir es in ngAfterViewInit () {} speichern, werden die this.form.valueChanges anscheinend immer aufgerufen, wenn einige Verarbeitungsketten für aktualisierte Daten in Formularen implementiert werden müssen.
Tài Nguyễn
1

Marks Vorschläge erweitern ...

Methode 3

Implementieren Sie eine "tiefe" Änderungserkennung im Modell. Die Vorteile bestehen hauptsächlich darin, dass keine Aspekte der Benutzeroberfläche in die Komponente integriert werden. Dadurch werden auch programmatische Änderungen am Modell erfasst. Das heißt, es würde zusätzliche Arbeit erfordern, um solche Dinge wie das von Thierry vorgeschlagene Entprellen zu implementieren, und dies wird auch Ihre eigenen programmatischen Änderungen erfassen, also seien Sie vorsichtig.

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

Versuchen Sie es in Plunker

N8allan
quelle
1

Für eckige 5+Version. Das Setzen der Version hilft, da der Winkel viele Änderungen vornimmt.

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

Ich dachte über die Verwendung der Methode (ngModelChange) nach, dachte dann über die FormBuilder-Methode nach und entschied mich schließlich für eine Variation von Methode 3. Dies erspart das Dekorieren der Vorlage mit zusätzlichen Attributen und übernimmt automatisch Änderungen am Modell, wodurch die Möglichkeit verringert wird, etwas zu vergessen mit Methode 1 oder 2.

Methode 3 etwas vereinfachen ...

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

Sie können eine Zeitüberschreitung hinzufügen, um doSomething () erst nach x Millisekunden aufzurufen, um das Entprellen zu simulieren.

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
quelle