Angular 7 - ControlValueAccessor - Trim input values that are binded to a form

user286974 picture user286974 · Apr 11, 2019 · Viewed 7.6k times · Source

We have a input field on a web page that must be trimmed at the same time as the user enter that data. As the input is binded to an Angular Form the value in the Form must also be trimmed. I use Angular 7

import {
  Directive,
  ElementRef,
  forwardRef,
  HostListener,
  Input,
  Renderer2
} from "@angular/core";
import {
  ControlValueAccessor,
  NG_VALUE_ACCESSOR
} from "@angular/forms";


@Directive({
  selector: "[ebppInputTextTrimmer]",
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => InputTextTrimmerDirective),
    multi: true
  }]
})
export class InputTextTrimmerDirective implements ControlValueAccessor {
  @Input() prevVal: string;

  @Input() isTrimEnabled: boolean;

  onChange = (_: any) => {
  }

  onTouched = () => {
  }

  constructor(
      private _renderer: Renderer2,
      private _elementRef: ElementRef) {
  }

  writeValue(value: any): void {
    const normalizedValue = value == null ? "" : value;
    this._renderer.setProperty(this._elementRef.nativeElement, "value", normalizedValue);
  }

  registerOnChange(fn: (_: any) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this._renderer.setProperty(this._elementRef.nativeElement, "disabled", isDisabled);
  }

  @HostListener("input", ["$event.target.value"])
  handleInput(inputValue: any): void {
    let valueToProcess = inputValue;
    if (this.isTrimEnabled) {
      valueToProcess = inputValue.trim();
    }

    this.onChange(valueToProcess);
    // set the value that is trimmed in the view
    this._renderer.setProperty(this._elementRef.nativeElement, "value", valueToProcess);
  }

}

The code shown works fine for me. I wonder if there a simpler solution.

Answer

Niraj picture Niraj · Apr 11, 2019

You can create a custom value accessor like below as a directive:

const TRIM_VAL_ACCESSOR = new Provider(
  NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => TrimValAccessor), multi: true});

@Directive({
  selector: 'input[trimval]',
  host: { '(keyup)': 'valOnChange($event.target)' },
  providers: [ TRIM_VAL_ACCESSOR ]
})
export class TrimValAccessor extends DefaultValueAccessor {
  onChange = (_) => {};
  onTouched = () => {};

  constructor(private renderer:Renderer) {
  }

  writeValue(value:any):void {
    if (value!=null) {
      super.writeValue(value.toString().trim());
    }
  }

  valOnChange(el) {
    let val = el.value.trim();
    this.renderer.setElementProperty(el, 'value', val);
    this.onChange(val);
  }
}

give referece in module:

declarations: [ TrimValAccessor ]

or in component like this

@Component({
  (...)
  template: `
    <input type="text" trimval/>
  `,
  directives: [ TrimValAccessor ]
})

use in input tag to trim the value

<input type="text" trimval/>