4

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

1
  • 1
    inject in constructor NgControl and change the value of ngControl, not the elementRef. It's possible you need store the position of selectionStart and selectionEnd to avoid the cursor goes always to end. some like this SO about mask:stackoverflow.com/questions/54460923/… Commented Dec 23, 2020 at 20:14

2 Answers 2

3
+50

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

Sign up to request clarification or add additional context in comments.

Comments

2

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.