Angular ngModelChange bị trễ khi cập nhật NgModel

Aug 31 2020

Tôi đang thực hiện một chỉ thị bằng cách sử dụng angle 8 để thực hiện một số xử lý và chuyển đổi văn bản thành chữ hoa. Mã đơn giản bên dưới:

html:

<input class="form-control" id="label" name="label" required myDirective>

chỉ thị:

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

Ngay bây giờ, chế độ xem được cập nhật chính xác, tuy nhiên mô hình bị trễ một bước. ví dụ: nếu văn bản đầu vào hiển thị 'AAAA' thì ng-reflect-modelsẽ hiển thị 'AAAa'.

Tôi đã tạo lại lỗi trong stackblitz: Lỗi được tạo lại trong Stackblitz

Bất kỳ ý tưởng nơi tôi sai?

Cam ơn trươc!

Trả lời

2 Eliseo Aug 31 2020 at 18:40

bạn có thể lấy nó bằng cách sử dụng

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

Hãy thấy rằng chúng tôi sử dụng đầu vào @HostListener để lấy mục chứ không chỉ giá trị. Điều này cho phép chúng tôi định vị con trỏ ở vị trí của nó sau khi thay đổi giá trị

LƯU Ý: Để tạo một chữ hoa đơn giản, tốt hơn nên sử dụng css text-biến đổi: chữ hoa và, khi chúng ta muốn nhận giá trị, hãy sử dụng toUpperCase ()

LƯU Ý 2: về mặt nạ xem SO này

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

Câu trả lời chi tiết

Hãy xem tại sao nó không hoạt động ban đầu.

Angular có các trình truy cập giá trị mặc định cho các phần tử nhất định, chẳng hạn như for input type='text', input type='checkbox'v.v.

A ControlValueAccessorlà trung gian giữa lớp VIEW và lớp MODEL. Khi người dùng nhập một đầu vào, CHẾ ĐỘ XEM sẽ thông báo cho ControlValueAccessor, chế độ này có nhiệm vụ thông báo cho MODEL.

Ví dụ, khi inputsự kiện xảy ra, onChangephương thức của ControlValueAccessorwill được gọi. Đây là giao onChange diện của mọi 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);
  });
}

Điều kỳ diệu xảy ra trong 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);là những gì gọi ngModelChangesự kiện trong chỉ thị tùy chỉnh. Điều này có nghĩa là giá trị mô hình là giá trị từ đầu vào (ở dạng chữ thường). Và vì ControlValueAccessor.writeValue chỉ ghi giá trị vào XEM, nên sẽ có độ trễ giữa giá trị của CHẾ ĐỘ XEM và giá trị của MÔ HÌNH.

Điều đáng nói là FormControl.setValue(val)sẽ ghi valvào cả hai lớp, VIEW và MODEL, nhưng nếu chúng ta sử dụng điều này, sẽ có một vòng lặp vô hạn , vì setValue()các cuộc gọi nội bộ viewToModelUpdate(vì MODEL phải được cập nhật) và viewToModelUpdatecác cuộc gọi setValue().

Hãy xem một giải pháp khả thi:

ngOnInit () {
  const initialOnChange = (this.ngControl.valueAccessor as any).onChange;

  (this.ngControl.valueAccessor as any).onChange = (value) => initialOnChange(this.processInput(value));
}

Với cách tiếp cận này, bạn đang sửa đổi dữ liệu của mình ở lớp XEM, trước khi nó được gửi đến ControlValueAccessor.

Và chúng tôi có thể chắc chắn rằng điều đó onChangetồn tại trên mọi cài đặt sẵn ControlValueAccessor.

Nếu bạn định tạo một tùy chỉnh, chỉ cần đảm bảo rằng nó có thuộc onChangetính. TypeScript có thể giúp bạn điều đó.

Nếu bạn muốn đọc thêm về nội bộ của @angular/forms, tôi khuyên bạn nên xem phần Khám phá kỹ lưỡng về Angular Forms .

OwenKelvin Aug 31 2020 at 18:46

Từ tài liệu

writeValue: Phương thức này được API biểu mẫu gọi để ghi vào dạng xem khi các thay đổi có lập trình từ mô hình sang dạng xem được yêu cầu.

Gọi phương thức này trên điều khiển do đó sẽ chỉ cập nhật chế độ xem. Để cập nhật mô hình, chúng ta có thể gọi viewToModelUpdate (value: string) trên lớp NgModel. Điều này sẽ cập nhật mô hình

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

Dưới đây là một giải pháp hoạt động trên Giải pháp làm việc stackblitz