MeVimalkumar
MeVimalkumar

Reputation: 3341

Custom directive for trimming whitespace in Angular 9

I have created a custom directive to remove whitespaces from the input box which works completely fine till Angular version 7, but not working in Angular version 9.

import {
    Directive,
    ElementRef,
    Output,
    EventEmitter,
} from '@angular/core';
@Directive({
    selector: '[trim]',
    host: {
        '(blur)': 'onBlur()'
    }
})
export class TrimDirective {
    @Output() ngModelChange: EventEmitter < any > = new EventEmitter();
    constructor(private element: ElementRef) {}
    onBlur() {
        (this.element.nativeElement as HTMLInputElement).value = (this.element.nativeElement as HTMLInputElement).value.trim();
        this.ngModelChange.emit((this.element.nativeElement as HTMLInputElement).value.trim());
    }
}

On blur event it supposed to trim the whitespaces and update the ngModel, but it's not updating ngModel

Upvotes: 3

Views: 9496

Answers (5)

MeVimalkumar
MeVimalkumar

Reputation: 3341

I know this is pretty late, but It's been around 3 years since I posted this question, and the answer given by radik is working well with template-driven forms but with Reactive forms, it is not working as expected. It trims the HTML control value, but it does not update the corresponding form control. So I'm posting this answer which works with Template driven forms as well as with reactive forms.

trim.directive.ts looks like follows

import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core';
import { NgControl } from '@angular/forms';

@Directive({
  selector: '[trim]'
})
export class TrimDirective {

  @Output() ngModelChange = new EventEmitter();

  constructor(private el: ElementRef,
    private control: NgControl) {
  }

  /**
   * Trim the input value on focus out of input component
   */
  @HostListener('focusout') onFocusOut() {
    (this.el.nativeElement as HTMLInputElement).value = (this.el.nativeElement as HTMLInputElement).value.trim();
    this.ngModelChange.emit(this.el.nativeElement.value)
    this.control.control?.setValue(this.el.nativeElement.value)
  }

}

And just use the trim directive in your HTML as below.

<input trim matInput name="name" id="name" formControlName="name"/>

This will not just update the HTML control it updates the angular form control as well and it emits the trimmed value on the ngModelChange event.

Upvotes: 8

hirenVaishnav
hirenVaishnav

Reputation: 39

Directive file:

import { Directive, ElementRef, HostListener } from '@angular/core';

@Directive({
  selector: '[appTrim]',
})
export class TrimDirective {
  debugger;

  constructor(
    private el: ElementRef
  ) { }

  @HostListener('blur') onBlur() {
    const value = this.el.nativeElement.value;
    if (value) {
      this.el.nativeElement.value = value.trim();
    }
  }
}

Html use:

 <input appTrim id="coxid52" type="text" class="form-control" ... />

Make sure to import that directive into the respective module as well.

Upvotes: 0

Oleg Pryatko
Oleg Pryatko

Reputation: 1

@Directive({
  selector: '[formControl], [formControlName]',
})
export class TrimDirective {
  constructor(private readonly control: NgControl) {}

  @HostListener('blur')
  onBlur(): void {
    if (typeof this.control.value === 'string') {
      this.control.control.setValue(this.control.value.trim());
    }
  }
}

Upvotes: 0

Julien Jacobs
Julien Jacobs

Reputation: 2629

Here is how we have implemented it, working from Angular 4.x to 11.x

As you will see, we are using selectors so that the directive is applied to all input / textarea elements without having to explicitly use the directive.

This can be easily changed and instead use the "app-trim"

import { Directive, HostListener, forwardRef } from "@angular/core";
import { DefaultValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";

const TRIM_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => TrimValueDirective),
  multi: true
};

/**
 * The trim accessor for writing trimmed value and listening to changes that is
 * used by the {@link NgModel}, {@link FormControlDirective}, and
 * {@link FormControlName} directives.
 */
@Directive({
  selector: `
    input:not([type=checkbox]):not([type=radio]):not([type=password]):not([readonly]):not(.app-trim-ignore)[formControlName],
    input:not([type=checkbox]):not([type=radio]):not([type=password]):not([readonly]):not(.app-trim-ignore)[formControl],
    input:not([type=checkbox]):not([type=radio]):not([type=password]):not([readonly]):not(.app-trim-ignore)[ngModel],
    textarea:not([readonly]):not(.app-trim-ignore)[formControlName],
    textarea:not([readonly]):not(.app-trim-ignore)[formControl],
    textarea:not([readonly]):not(.app-trim-ignore)[ngModel],
    :not([readonly]):not(.app-trim-ignore)[ngDefaultControl],
    [app-trim]
  `,
  providers: [TRIM_VALUE_ACCESSOR]
})
export class TrimValueDirective extends DefaultValueAccessor {

  @HostListener("input", ["$event.target.value"])
  ngOnChange = (val: string): void => {
    this.onChange(val.trim());
  };

  @HostListener("blur", ["$event.target.value"])
  applyTrim(val: string): void {
    this.writeValue(val.trim());
  }

  writeValue(value: any): void {
    if (typeof value === "string") {
      value = value.trim();
    }

    super.writeValue(value);
  }

}

Upvotes: 6

Radik
Radik

Reputation: 2795

to update input value they use both setProperty() and setAttribute()

import { Directive, EventEmitter, Input, ChangeDetectorRef, Output, ElementRef, HostListener, Inject, Renderer2 } from '@angular/core';
import { NgModel } from '@angular/forms';

@Directive({
  selector: '[appTrim]'
})
export class TrimDirective {
  constructor(
    private renderer: Renderer2,
    private elementRef: ElementRef,
    private ngModel: NgModel) { }

  @HostListener("blur")
  onBlur() {
    let value = this.ngModel.model;

    if(value) {
      value = value.trim();
      this.renderer.setProperty(
        this.elementRef.nativeElement, "value", value);
      this.renderer.setAttribute(
        this.elementRef.nativeElement, "value", value);
      this.ngModel.update.emit(value);
    } else {
      this.renderer.setProperty(
        this.elementRef.nativeElement, "value", null);
      this.renderer.setAttribute(
        this.elementRef.nativeElement, "value", null);
      this.ngModel.update.emit("");
    }
  }
}

example link

Upvotes: 3

Related Questions