Cara memperhatikan perubahan bentuk di Angular


151

Di Angular, saya mungkin memiliki bentuk yang terlihat seperti ini:

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

Di dalam pengontrol yang sesuai, saya dapat dengan mudah melihat perubahan pada isi formulir itu seperti:

function($scope) {

    $scope.model = {};

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

}

Berikut ini adalah contoh Angular pada JSFiddle .

Saya mengalami kesulitan mencari tahu cara menyelesaikan hal yang sama di Angular. Jelas, kita tidak lagi memiliki $scope, $ rootScope. Tentunya ada metode di mana hal yang sama dapat dicapai?

Berikut adalah contoh sudut pada Plunker .


alih-alih menonton beberapa data dari pengontrol Anda, saya pikir Anda harus memecat suatu peristiwa (seperti ng-perubahan lama) dari formulir Anda.
Deblaton Jean-Philippe

Namun, alasan untuk menghapus ruang lingkup adalah untuk menyingkirkan para pengamat. Saya tidak berpikir ada arloji tersembunyi di suatu tempat di sudut 2
Deblaton Jean-Philippe

4
Jika saya memahami Anda dengan benar, Anda menyarankan agar saya menambahkan (ngModelChange)="onModelChange($event)"atribut ke setiap input formulir untuk mencapai ini?
tambler

1
Apakah bentuk contoh itu sendiri tidak memancarkan semacam perubahan acara? Jika demikian, bagaimana Anda mengaksesnya?
tambler

Jika saya yakin tentang apa yang harus dilakukan, saya akan menjawab alih-alih berkomentar. Saya belum menggunakan angular 2.0, tetapi dari apa yang saya baca, arloji telah sepenuhnya menghilang untuk memiliki kerangka kerja berbasis peristiwa (alih-alih arloji dalam yang dilakukan pada setiap intisari)
Deblaton Jean-Philippe

Jawaban:


189

UPD. Jawaban dan demo diperbarui untuk menyelaraskan dengan Angular terbaru.


Anda bisa berlangganan ke seluruh perubahan formulir karena fakta bahwa FormGroup yang mewakili formulir menyediakan valueChangesproperti yang merupakan contoh yang dapat Diperhatikan :

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

Dalam hal ini Anda perlu membuat form secara manual menggunakan FormBuilder . Sesuatu seperti ini:

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

Lihat valueChangesberaksi dalam demo ini : http://plnkr.co/edit/xOz5xaQyMlRzSrgtt7Wn?p=preview


2
Ini sangat dekat dengan sasaran. Untuk mengonfirmasi - apakah Anda memberi tahu saya bahwa tidak mungkin berlangganan valueChangesemitor acara jika formulir itu hanya ditentukan di dalam templat? Dengan kata lain - dalam konstruktor komponen, apakah tidak mungkin untuk mendapatkan referensi ke formulir yang ditentukan hanya dalam templat komponen itu dan tidak dengan bantuan FormBuilder?
tambler

Ini adalah jawaban yang benar. Jika Anda tidak memiliki pembuat formulir Anda melakukan formulir yang digerakkan templat. mungkin ada cara untuk tetap mendapatkan formulir yang disuntikkan tetapi jika Anda ingin formulir yang dapat diobservasi. nilai Perubahan Anda harus menggunakan formBuilder dan meninggalkan model
Universitas Angular

2
@tambler, Anda bisa mendapatkan referensi ke NgForm menggunakan @ViewChild(). Lihat jawaban saya yang diperbarui.
Mark Rajcok

1
Anda tidak perlu berhenti berlangganan saat menghancurkan?
Bazinga

1
@galvan Saya tidak berpikir akan ada kebocoran. Bentuknya adalah bagian dari komponen dan akan dibuang dengan benar pada semua bidang dan pendengar acara.
dfsq

107

Jika Anda menggunakan FormBuilder, lihat jawaban @ dfsq.

Jika Anda tidak menggunakan FormBuilder, ada dua cara untuk diberitahu tentang perubahan.

Metode 1

Seperti dibahas dalam komentar pada pertanyaan, gunakan peristiwa yang mengikat pada setiap elemen input. Tambahkan ke templat Anda:

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

Kemudian di komponen Anda:

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

The Bentuk Halaman memiliki beberapa informasi tambahan tentang ngModel yang relevan di sini:

Ini ngModelChangebukan <input>acara elemen. Ini sebenarnya merupakan properti acara dari NgModelarahan. Ketika Angular melihat target yang mengikat dalam formulir [(x)], ia mengharapkan xarahan untuk memiliki xproperti input dan xChangeproperti output.

Keanehan lainnya adalah ekspresi template model.name = $event,. Kami terbiasa melihat $eventobjek yang berasal dari acara DOM. Properti ngModelChange tidak menghasilkan acara DOM; ini adalah EventEmitterproperti Angular yang mengembalikan nilai kotak input saat diaktifkan ..

Kami hampir selalu lebih suka [(ngModel)]. Kami mungkin membagi penjilidan jika kami harus melakukan sesuatu yang istimewa dalam penanganan acara seperti debounce atau throttle stroke kunci.

Dalam kasus Anda, saya kira Anda ingin melakukan sesuatu yang istimewa.

Metode 2

Tentukan variabel templat lokal dan setel ke ngForm.
Gunakan ngControl pada elemen input.
Dapatkan referensi ke arahan NgForm formulir menggunakan @ViewChild, kemudian berlangganan ControlGroup NgForm untuk perubahan:

<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

Untuk informasi lebih lanjut tentang Metode 2, lihat video Savkin .

Lihat juga @ jawaban Thierry untuk informasi lebih lanjut tentang apa yang dapat Anda lakukan dengan yang valueChangesdapat diamati (seperti melonggarkan / menunggu sedikit sebelum memproses perubahan).


61

Untuk melengkapi sedikit lebih banyak jawaban hebat sebelumnya, Anda perlu menyadari bahwa bentuk leverage dapat diamati untuk mendeteksi dan menangani perubahan nilai. Itu sesuatu yang sangat penting dan kuat. Baik Mark dan dfsq menggambarkan aspek ini dalam jawaban mereka.

Dapat diamati tidak hanya menggunakan subscribemetode (sesuatu yang mirip dengan thenmetode janji di Angular 1). Anda dapat melangkah lebih jauh jika diperlukan untuk mengimplementasikan beberapa rantai pemrosesan untuk data yang diperbarui dalam bentuk.

Maksud saya Anda dapat menentukan pada level ini waktu debounce dengan debounceTimemetode. Ini memungkinkan Anda untuk menunggu beberapa saat sebelum menangani perubahan dan menangani beberapa input dengan benar:

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

Anda juga dapat langsung menyambungkan pemrosesan yang ingin Anda picu (beberapa asinkron misalnya) ketika nilai diperbarui. Misalnya, jika Anda ingin menangani nilai teks untuk memfilter daftar berdasarkan permintaan AJAX, Anda dapat memanfaatkan switchMapmetode ini:

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

Anda bahkan melangkah lebih jauh dengan menautkan hasil yang dapat diamati secara langsung ke properti komponen Anda:

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

dan tampilkan menggunakan asyncpipa:

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

Hanya untuk mengatakan bahwa Anda perlu memikirkan cara untuk menangani formulir secara berbeda di Angular2 (cara yang jauh lebih kuat ;-)).

Semoga ini bisa membantu Anda, Thierry


Properti 'valueChanges' tidak ada pada tipe 'string'
Toolkit

Saya terjebak, dalam file TS, di mana metode perubahan formulir cek harus ditempatkan? Jika kita menyimpannya di ngAfterViewInit () {}, Tampaknya this.form.valueChanges selalu memanggil jika kita perlu mengimplementasikan beberapa rantai pemrosesan untuk data yang diperbarui dalam bentuk.
Tài Nguyễn

1

Memperluas saran Mark ...

Metode 3

Terapkan deteksi perubahan "dalam" pada model. Keuntungannya terutama melibatkan penghindaran memasukkan aspek antarmuka pengguna ke dalam komponen; ini juga menangkap perubahan program yang dibuat pada model. Karena itu, akan membutuhkan kerja ekstra untuk mengimplementasikan hal-hal seperti debouncing seperti yang disarankan oleh Thierry, dan ini juga akan menangkap perubahan program Anda sendiri , jadi gunakan dengan hati-hati.

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

Coba di Plunker


1

Untuk 5+versi sudut . Menempatkan versi membantu karena sudut membuat banyak perubahan.

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

Saya berpikir tentang menggunakan metode (ngModelChange), kemudian berpikir tentang metode FormBuilder, dan akhirnya memilih variasi Metode 3. Ini menyimpan dekorasi template dengan atribut tambahan dan secara otomatis mengambil perubahan pada model - mengurangi kemungkinan melupakan sesuatu dengan Metode 1 atau 2.

Metode Penyederhanaan 3 sedikit ...

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

Anda bisa menambahkan batas waktu untuk hanya memanggil doSomething () setelah x jumlah milidetik untuk mensimulasikan debounce.

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));
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.