nativeElement.Value changes in directive not being reflected for input/ngModelChange events at the moment

4.7k views Asked by At

Trying to replace a special character of input, I ended up writing this simple directive:

normalized-input.directive.ts

@Directive({
  selector: "[appNormalizedInput]"
})
export class NormalizedInputDirective {
  constructor(private element: ElementRef) {}

  @HostListener("keypress", ["$event"]) replaceAWithB(event): void {
    const initalValue: string = this.element.nativeElement.value;
    this.element.nativeElement.value = initalValue.replace("a", "b");
  }
}

This replaces a with b on keypress. Here is my sample (StackBlitz):

app.component.html

<input type="text" (input)="onInput($event)" [(ngModel)]="model" (ngModelChange)="onModelChange()" appNormalizedInput/>
<br/>
<label>{{model}}</label>

app.component.ts

export class AppComponent {
  model = "";

  onInput(event) {
    console.log("on input: ", event.target.value);
  }

  onModelChange() {
    console.log("On model change: ", this.model);
  }
}

Once I enter a, I expect b in console output and also the same for model (label content) but I get a till the next key is pressed. The problem is that events are one step behind the actual UI value of input.

What is the correct HostListener event for handling this scenario? And how should I change the value so that I can get the fresh one in (input) and (ngModelChange) events?

StackBlitz

2

There are 2 answers

0
Nima Afzal Ahangaran On BEST ANSWER

You should use ngControl and wrap the onChange event with your desired proxy function as follow:

@Directive({
  selector: "[myDirective]"
})
export class Mydirective {
  constructor(private ngControl: NgControl) {}

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

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

  processInput(value: any) {
    return value.replace("a", "b");
  }

  @HostListener("ngModelChange", ["$event"])
  ngModelChange(value: any) {
    this.ngControl.valueAccessor.writeValue(this.processInput(value));
  }
}

StackBlitz

0
yurzui On

If you still want to do it by handling keypress event and also want to preserve cursor position while typing then you can try this option:

@HostListener("keypress", ["$event"]) replaceAWithB(e): void {
  if (e.key === 'a') {
    const { selectionStart: start, selectionEnd: end, value: oldValue } = e.target;

    e.target.value = oldValue.slice(0, start) + 'b' + oldValue.slice(end);
    e.target.selectionStart = e.target.selectionEnd = start + 1;

    e.preventDefault();
    e.target.dispatchEvent(new KeyboardEvent('input'));
  }
}

Forked Stackblitz