Search code examples
angularangular2-directivesangular-ngmodelngmodelangular-input

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


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


Solution

  • 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