NgModel을 업데이트 할 때 Angular ngModelChange가 늦습니다.

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 in 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 : uppercase를 사용하는 것이 더 좋으며 값을 얻으려면 toUpperCase ()를 사용하십시오.

비고 2 : 약이 볼 수 마스크 SO를

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

자세한 답변

처음에는 작동하지 않는 이유를 살펴 보겠습니다.

각도는이 기본 값 접근 등을위한 특정 요소에 대한 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 단지가 보기에 값을 기록하는있을 것 지연 뷰의 값과 모델의 값 사이.

그 언급이의 가치는 FormControl.setValue(val)쓸 것이다 val 층, VIEW 및 모델,하지만 우리는이를 사용했다 경우,이있을 것입니다 무한 루프 , 이후 setValue()내부적으로 호출 viewToModelUpdate(모델을 업데이트하기 때문에)와 viewToModelUpdate통화를 setValue().

가능한 해결책을 살펴 보겠습니다.

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

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

이 접근 방식을 사용하면 VIEW 레이어에서 데이터를 수정하고 ControlValueAccessor.

그리고 우리는 그것이 onChange모든 내장 ControlValueAccessor.

사용자 지정 항목을 만들려면 onChange속성 이 있는지 확인하십시오 . TypeScript는 당신을 도울 수 있습니다.

의 내부에 대해 더 읽고 싶다면 Angular Forms의 철저한 탐색을@angular/forms 살펴 보는 것이 좋습니다 .

OwenKelvin Aug 31 2020 at 18:46

문서에서

writeValue : 이 메소드는 모델에서보기로 프로그래밍 방식 변경이 요청 될 때보 기에 쓰기 위해 양식 API에 의해 호출됩니다.

따라서 컨트롤에서이 메서드를 호출하면 뷰만 업데이트됩니다. 모델을 업데이트하기 위해 NgModel 클래스에서 viewToModelUpdate (value : string) 를 호출 할 수 있습니다 . 그러면 모델이 업데이트됩니다.

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의 작업 솔루션입니다.