Angular ngModelChange ล่าช้าเมื่ออัปเดต NgModel
ฉันกำลังสร้างคำสั่งโดยใช้ angular 8 เพื่อทำการประมวลผลและแปลงข้อความเป็นตัวพิมพ์ใหญ่ โค้ดแบบง่ายด้านล่าง:
html:
<input class="form-control" id="label" name="label" required myDirective>
คำสั่ง:
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));
}
}
ขณะนี้มุมมองได้รับการอัปเดตอย่างถูกต้องอย่างไรก็ตามโมเดลล่าช้าไปทีละขั้นตอน ตัวอย่างเช่นหากข้อความที่ป้อนแสดงเป็น 'AAAA' ng-reflect-modelจะแสดง 'AAAa'
ฉันได้จำลองข้อผิดพลาดใน stackblitz: Error Reproduced ใน Stackblitz
ความคิดใดที่ฉันผิด?
ขอบคุณก่อน!
คำตอบ
คุณสามารถใช้งานได้
@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
}
ดูว่าเราใช้อินพุต @HostListener เพื่อรับไอเท็มไม่ใช่แค่ค่าเท่านั้น สิ่งนี้ช่วยให้เราวางเคอร์เซอร์ในตำแหน่งของเขาหลังจากเปลี่ยนค่า
หมายเหตุ: ในการสร้างตัวพิมพ์ใหญ่อย่างง่ายควรใช้ css text-transform: ตัวพิมพ์ใหญ่และเมื่อเราต้องการรับค่าใช้ toUpperCase ()
NOTE2: เกี่ยวกับหน้ากากดูดังนั้น
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));
}
คำตอบโดยละเอียด
มาดูกันว่าทำไมถึงไม่ได้ผลในตอนแรก
เชิงมุมมีaccessors ค่าเริ่มต้นสำหรับองค์ประกอบบางอย่างเช่นสำหรับinput type='text', input type='checkbox'ฯลฯ ...
A ControlValueAccessorคือคนกลางระหว่างเลเยอร์ VIEW และเลเยอร์ MODEL เมื่อผู้ใช้พิมพ์ข้อมูลเข้า VIEW จะแจ้งให้ทราบControlValueAccessorซึ่งมีงานที่จะแจ้งให้ MODEL ทราบ
ตัวอย่างเช่นเมื่อinputเหตุการณ์เกิดขึ้นจะมีการเรียกonChangeใช้เมธอด นี่คือลักษณะของทุกๆ :ControlValueAccessor onChange 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);
});
}
ความมหัศจรรย์เกิดขึ้นใน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);คือสิ่งที่เรียกngModelChangeเหตุการณ์ในคำสั่งที่กำหนดเอง สิ่งนี้หมายความว่าค่าโมเดลคือค่าจากอินพุต (เป็นตัวพิมพ์เล็ก) และเนื่องจากเขียนControlValueAccessor.writeValue เฉพาะค่าไปยัง VIEW จึงมีความล่าช้าระหว่างค่าของ VIEW และค่าของ MODEL
มันมูลค่าการกล่าวขวัญว่าFormControl.setValue(val)จะเขียนvalไปทั้งชั้นดูและ MODEL แต่ถ้าเราจะนำมาใช้นี้จะมีวง จำกัดเนื่องจากsetValue()ภายในสายviewToModelUpdate(เพราะรูปแบบจะต้องมีการปรับปรุง) และโทรviewToModelUpdatesetValue()
มาดูวิธีแก้ปัญหาที่เป็นไปได้:
ngOnInit () {
const initialOnChange = (this.ngControl.valueAccessor as any).onChange;
(this.ngControl.valueAccessor as any).onChange = (value) => initialOnChange(this.processInput(value));
}
ด้วยวิธีนี้คุณกำลังแก้ไขข้อมูลของคุณที่เลเยอร์ VIEW ก่อนที่จะส่งไปยังไฟล์ControlValueAccessor.
และเราสามารถมั่นใจได้ว่ามีอยู่ในทุกตัวในonChangeControlValueAccessor
หากคุณกำลังจะสร้างแบบกำหนดเองเพียงตรวจสอบให้แน่ใจว่ามีonChangeคุณสมบัติ TypeScript สามารถช่วยคุณได้
หากคุณต้องการอ่านเพิ่มเติมเกี่ยวกับ internals ของ@angular/formsผมอยากแนะนำให้มีลักษณะที่สำรวจอย่างละเอียดของรูปแบบเชิงมุม
จากเอกสารประกอบ
writeValue:เมธอดนี้เรียกโดยฟอร์ม API เพื่อเขียนไปยังมุมมองเมื่อมีการร้องขอการเปลี่ยนแปลงทางโปรแกรมจากโมเดลเป็นมุมมอง
การเรียกใช้เมธอดนี้บนตัวควบคุมจึงจะอัปเดตมุมมองเท่านั้น ในการอัปเดตโมเดลเราสามารถเรียกviewToModelUpdate (value: string)บนคลาส NgModel การดำเนินการนี้จะอัปเดตโมเดล
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));
}
ด้านล่างนี้เป็นโซลูชันการทำงานบน stackblitz Working Solution