Angular'da form değişiklikleri nasıl izlenir


151

Angular'da şöyle görünen bir form olabilir:

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

İlgili denetleyicide, bu formun içeriğindeki değişiklikleri kolayca izleyebilirim:

function($scope) {

    $scope.model = {};

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

}

İşte JSFiddle'da bir Açısal örnek .

Aynı şeyi Angular'da nasıl başaracağımı bulmakta zorlanıyorum. Açıkçası artık elimizde $scope$ rootScope yok. Elbette aynı şeyin gerçekleştirilebileceği bir yöntem var mı?

İşte Plunker'da Açısal bir örnek .


denetleyicinizden bazı verileri izlemek yerine, formunuzdan bir olayı (eski ng-change gibi) tetiklemeniz gerektiğini düşünüyorum.
Deblaton Jean-Philippe

btw, kapsamı kaldırmanın nedeni, bu izleyicilerden kurtulmaktır. Açısal 2'de bir yerde gizli bir saat olduğunu sanmıyorum
Deblaton Jean-Philippe

4
Sizi doğru anlıyorsam, bunu gerçekleştirmek (ngModelChange)="onModelChange($event)"için her form girişine bir öznitelik eklememi önerirsiniz?
Tambler

1
Form örneğinin kendisi bir tür değişiklik olayı yaymıyor mu? Öyleyse, ona nasıl erişirsiniz?
Tambler

Ne yapacağımdan eminsem, yorum yapmak yerine cevap verirdim. Henüz açısal 2.0 kullanmadım, ancak okuduğumdan, saat, olay tabanlı bir çerçeveye sahip olmak için tamamen kayboldu (her
özette

Yanıtlar:


189

UPD. Cevap ve demo en son Angular ile hizalanacak şekilde güncellenir.


Bir formu temsil eden FormGroup'unvalueChanges Observerable örneği olan özellik sağlaması nedeniyle tüm form değişikliklerine abone olabilirsiniz :

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

Bu durumda, FormBuilder kullanarak el ile form oluşturmanız gerekir . Bunun gibi bir şey:

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

valueChangesBu demoda aksiyona göz atın : http://plnkr.co/edit/xOz5xaQyMlRzSrgtt7Wn?p=preview


2
Bu işarete çok yakın. Onaylamak için - Bunun olduğunu bana söylüyorsun değil bir formun abone olmak mümkün valueChangesolay yayıcı eğer form şablonu içinde yalnızca tanımlanır? Başka bir deyişle - bir bileşenin yapıcısı içinde, FormBuilder yardımı ile değil, yalnızca o bileşenin şablonu içinde tanımlanan bir forma başvuru yapmak mümkün müdür?
Tambler

Bu doğru cevap. Form oluşturucunuz yoksa, şablona dayalı formlar hazırlıyorsunuz demektir. Muhtemelen formu enjekte etmenin bir yolu vardır, ancak Gözlemlenebilir form.valueChanges'i istiyorsanız, kesinlikle formBuilder kullanmalı ve ng-modelini terk etmelisiniz
Angular University

2
@tambler, kullanarak NgForm referans alabilirsiniz @ViewChild(). Güncellenmiş cevabımı görün.
Mark Rajcok

1
Yok etme işleminden çıkmanıza gerek yok mu?
Bazinga

1
@galvan Sızıntı olabileceğini sanmıyorum. Form, bileşenin bir parçasıdır ve tüm alanları ve olay dinleyicileriyle imha üzerine düzgün bir şekilde atılacaktır.
dfsq

107

Kullanıyorsanız FormBuilder, @ dfsq'nin cevabına bakınız.

Eğer kullanmıyorsanız FormBuilder, değişikliklerden haberdar olmak için iki yol vardır.

Yöntem 1

Soru hakkındaki yorumlarda tartışıldığı gibi , her bir giriş elemanı için bir olay bağlayıcı kullanın . Şablonunuza ekleyin:

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

Sonra bileşeninizde:

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

Formlar sayfa burada alakalı ngModel hakkında ek bilgiler bulunmaktadır:

ngModelChangeBir değil <input>öğesi etkinliği. Aslında bu NgModeldirektifin bir olay özelliğidir . Angular formda bir bağlayıcı hedef gördüğünde [(x)], xdirektifinx input özelliği ve xChangeoutput özelliği .

Diğer gariplik ise şablon ifadesidir model.name = $event. $eventBir DOM etkinliğinden gelen bir nesneyi görmeye alışkınız. NgModelChange özelliği bir DOM olayı oluşturmaz; tetiklendiğinde EventEmittergiriş kutusu değerini döndüren bir Açısal özelliktir.

Neredeyse hep tercih ediyoruz [(ngModel)]. Olay işlemede, tuş vuruşlarını azaltma veya azaltma gibi özel bir şey yapmamız gerektiğinde bağlamayı bölebiliriz.

Sizin durumunuzda, özel bir şey yapmak istediğinizi varsayalım.

Yöntem 2

Yerel bir şablon değişkeni tanımlayın ve olarak ayarlayın ngForm.
Giriş elemanlarında ngControl kullanın.
@ViewChild kullanarak formun NgForm yönergesine bakın, ardından değişiklikler için NgForm'un Kontrol Grubu'na abone olun:

<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

Yöntem 2 hakkında daha fazla bilgi için Savkin'in videosuna bakın .

Ayrıca, valueChangesgözlemlenebilir ile neler yapabileceğiniz hakkında daha fazla bilgi için @ Thierry'nin cevabına bakınız (değişiklikleri işlemeden önce biraz geri dönme / biraz bekleme gibi).


61

Biraz daha önceki harika cevapları tamamlamak için, değer değişikliklerini tespit etmek ve işlemek için gözlemlenebilir özelliklerden yararlandığını bilmeniz gerekir. Gerçekten önemli ve güçlü bir şey. Hem Mark hem de dfsq bu yönü cevaplarında anlattılar.

Gözlemlenebilirler sadece subscribeyöntemi kullanmaya izin vermez ( thenAçısal 1'deki vaat yöntemine benzer bir şey ). Formlardaki güncellenmiş veriler için bazı işleme zincirlerini uygulamak için gerekirse daha da ileri gidebilirsiniz.

Demek istediğim, bu seviyede geri dönme süresini debounceTimeyöntemle belirtebilirsiniz . Bu, değişikliği işlemeden önce bir süre beklemenizi ve birkaç girişi doğru şekilde işlemenizi sağlar:

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

Değerler güncellendiğinde, tetiklemek istediğiniz işlemi doğrudan da ekleyebilirsiniz (örneğin, bazı eşzamansız olanları). Örneğin, bir listeyi AJAX isteğine göre filtrelemek için bir metin değerini işlemek istiyorsanız, switchMapyöntemi kullanabilirsiniz:

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

İade edilen gözlemlenebilir öğeyi doğrudan bileşeninizin bir özelliğine bağlayarak daha da ileri gidebilirsiniz:

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

ve asyncboruyu kullanarak görüntüleyin :

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

Sadece Angular2'de formları farklı şekilde ele almanın yolunu düşünmeniz gerektiğini söylemek için (çok daha güçlü bir yol ;-)).

Umarım sana yardımcı olur, Thierry


'StringChanges' türünde 'valueChanges' özelliği mevcut değil
Toolkit

TS dosyasında, kontrol formu değiştirme yönteminin nereye yerleştirilmesi gerektiğini sapladım? Bunu ngAfterViewInit () {} 'de saklarsak, formlarda güncellenmiş veriler için bazı işleme zincirlerini uygulamamız gerektiğinde this.form.valueChanges her zaman çağırır gibi görünüyor.
Tài Nguyễn

1

Mark'ın önerilerini genişletmek ...

Yöntem 3

Model üzerinde "derin" değişiklik tespiti uygulayın. Avantajlar öncelikle kullanıcı arabirimi unsurlarının bileşene dahil edilmesinden kaçınılmasını; bu, model üzerinde yapılan programlı değişiklikleri de yakalar. Bununla birlikte, Thierry tarafından önerildiği gibi geri dönme gibi şeyleri uygulamak için ekstra iş gerektirecektir ve bu da kendi programatik değişikliklerinizi yakalayacaktır , bu yüzden dikkatli kullanı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];
      }
    }
  }

Plunker'da deneyin


1

Açısal 5+versiyon için. Sürüm koymak açısal olarak yardımcı olur çok değişiklik yapar.

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

(NgModelChange) yöntemini kullanmayı düşündüm, sonra FormBuilder yöntemini düşündüm ve sonunda Yöntem 3'ün bir varyasyonuna karar verdim. Yöntem 1 veya 2 ile.

Yöntem 3'ü biraz basitleştirmek ...

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

Geri dönmeyi simüle etmek için yalnızca x milisaniye sayısından sonra doSomething () öğesini çağırmak için bir zaman aşımı süresi ekleyebilirsiniz.

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));
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.