Angular ngModelChange ล่าช้าเมื่ออัปเดต NgModel

Aug 31 2020

ฉันกำลังสร้างคำสั่งโดยใช้ 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

ความคิดใดที่ฉันผิด?

ขอบคุณก่อน!

คำตอบ

2 Eliseo Aug 31 2020 at 18:40

คุณสามารถใช้งานได้

  @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: เกี่ยวกับหน้ากากดูดังนั้น

9 AndreiGătej Aug 31 2020 at 19:26

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ผมอยากแนะนำให้มีลักษณะที่สำรวจอย่างละเอียดของรูปแบบเชิงมุม

OwenKelvin Aug 31 2020 at 18:46

จากเอกสารประกอบ

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