Angular ngModelChange terlambat saat memperbarui NgModel
Saya membuat arahan menggunakan angular 8 untuk melakukan beberapa pemrosesan dan mengubah teks menjadi huruf besar. Kode yang disederhanakan di bawah ini:
html:
<input class="form-control" id="label" name="label" required myDirective>
pengarahan:
import { Directive, HostListener } from '@angular/core';
import { NgControl } from '@angular/forms';
@Directive({
selector: '[myDirective]'
})
export class Mydirective {
constructor(private control: NgControl) { }
processInput(value: any) {
// do some formatting
return value.toUpperCase();
}
@HostListener('ngModelChange', ['$event'])
ngModelChange(value: any) {
this.control.valueAccessor.writeValue(this.processInput(value));
}
}
Saat ini, tampilan diperbarui dengan benar, namun modelnya terlambat satu langkah. misalnya: jika teks input menunjukkan 'AAAA' maka ng-reflect-modelakan menampilkan 'AAAa'.
Saya telah mereproduksi kesalahan di stackblitz: Error Reproduced in Stackblitz
Tahu di mana saya salah?
Terima kasih sebelumnya!
Jawaban
Anda bisa mendapatkannya menggunakan
@HostListener('input', ['$event'])
ngModelChange(event: any) {
const item = event.target
const value = item.value;
const pos = item.selectionStart;
this.control.control.setValue(this.processInput(value), { emit: false });
item.selectionStart = item.selectionEnd = pos
}
Lihat bahwa kita menggunakan input @HostListener, untuk mendapatkan itemnya, bukan hanya nilainya. Ini memungkinkan kita memposisikan kursor pada posisinya setelah mengubah nilainya
CATATAN: Untuk membuat huruf besar sederhana lebih baik gunakan css text-transform: uppercase dan, ketika kita ingin mendapatkan nilainya gunakan toUpperCase ()
CATATAN2: tentang topeng lihat SO ini
TLDR
StackBlitz .
my-directive.directive.ts
/* ... */
ngOnInit () {
const initialOnChange = (this.ngControl.valueAccessor as any).onChange;
(this.ngControl.valueAccessor as any).onChange = (value) => initialOnChange(this.processInput(value));
}
/* ... */
@HostListener('ngModelChange', ['$event'])
ngModelChange(value: any) {
this.ngControl.valueAccessor.writeValue(this.processInput(value));
}
Jawaban rinci
Mari kita lihat mengapa itu tidak berhasil pada awalnya.
Angular memiliki pengakses nilai default untuk elemen tertentu, seperti untuk input type='text', input type='checkbox'dll ...
A ControlValueAccessoradalah perantara antara layer VIEW dan layer MODEL. Saat pengguna mengetik ke dalam input, VIEW memberitahu ControlValueAccessor, yang memiliki tugas untuk menginformasikan MODEL.
Misalnya, ketika inputperistiwa terjadi, onChangemetode ControlValueAccessorakan dipanggil. Berikut onChange tampilannya untuk setiap ControlValueAccessor :
function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
dir.valueAccessor!.registerOnChange((newValue: any) => {
control._pendingValue = newValue;
control._pendingChange = true;
control._pendingDirty = true;
if (control.updateOn === 'change') updateControl(control, dir);
});
}
Keajaiban terjadi di updateControl:
function updateControl(control: FormControl, dir: NgControl): void {
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
// !
dir.viewToModelUpdate(control._pendingValue);
control._pendingChange = false;
}
dir.viewToModelUpdate(control._pendingValue);adalah yang memanggil ngModelChangeperistiwa di custom directive. Artinya, nilai model adalah nilai dari input (dalam huruf kecil). Dan karena ControlValueAccessor.writeValue hanya menulis nilai ke VIEW, maka akan terjadi penundaan antara nilai VIEW dan nilai MODEL.
Perlu disebutkan bahwa FormControl.setValue(val)akan menulis valke kedua lapisan, VIEW dan MODEL, tetapi jika kita menggunakan ini, akan ada loop tak terbatas , karena setValue()panggilan internal viewToModelUpdate(karena MODEL harus diperbarui), dan viewToModelUpdatepanggilan setValue().
Mari kita lihat solusi yang mungkin:
ngOnInit () {
const initialOnChange = (this.ngControl.valueAccessor as any).onChange;
(this.ngControl.valueAccessor as any).onChange = (value) => initialOnChange(this.processInput(value));
}
Dengan pendekatan ini, Anda memodifikasi data Anda di lapisan VIEW, sebelum dikirim ke ControlValueAccessor.
Dan kita bisa yakin itu onChangeada di setiap built-in ControlValueAccessor.
Jika Anda akan membuat yang khusus, pastikan saja itu memiliki onChangeproperti. TypeScript dapat membantu Anda dengan itu.
Jika Anda ingin membaca lebih lanjut tentang bagian dalam dari @angular/forms, saya sarankan untuk melihat eksplorasi menyeluruh tentang Bentuk Sudut .
Dari dokumentasi
writeValue: Metode ini dipanggil oleh API formulir untuk menulis ke tampilan saat perubahan programatik dari model ke tampilan diminta.
Memanggil metode ini pada kontrol hanya akan memperbarui tampilan. Untuk memperbarui model kita dapat memanggil viewToModelUpdate (nilai: string) pada kelas NgModel. Ini akan memperbarui model
constructor(private model: NgModel) { }
processInput(value: any) {
// do some formatting
return value.toUpperCase();
}
@HostListener('input', ['$event'])
ngModelChange(input: any) {
const value = input.target.value
this.model.valueAccessor.writeValue(this.processInput(value));
this.model.viewToModelUpdate(this.processInput(value));
}
Di bawah ini adalah solusi yang berfungsi pada Solusi Kerja stackblitz