12

I want to create Angular 2 directive that will tirm spaces only from begining and the end of the text that user has typed into input field.

I have input field

<input trim name="fruit" [(ngModel)]="fruit"/>

and directive

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

@Directive({
  selector: 'input[trim]',
  host: {'(blur)': 'onChange($event)'}
})

export class TrimWhiteSpace {

  constructor(private cdRef:ChangeDetectorRef, private el: ElementRef){}

  onChange($event:any) {
    let theEvent = $event || window.event;
    theEvent.target.value = theEvent.target.value.trim();
  }
}

which works fine, removes spaces and changes text in input field, but the problem is that the value in ngModel variable "fruit" is not changed and it still contains text with spaces at the begining or at the end.

I also tried to add following to onChange method

this.el.nativeElement.value = theEvent.trim();
this.cdRef.detectChanges();

and change form (blur) to (ngModelChange), but text in ngModel is not effected.

Any suggestions?

misterGrosar
  • 294
  • 1
  • 3
  • 10

9 Answers9

19

to avoid confusion changing model attribute name.

<input name="fruit" [(ngModel)]="fruit1" (change)="fruit1=fruit1.trim()"/>
6

Have you looked at https://github.com/anein/angular2-trim-directive ?

Seems like it would address your use case

Vijay
  • 209
  • 4
  • 18
  • Thank you for your answer! This directory only partialy adresses my problem. It removes all spaces, what I need is such directive that allowes user to input text with multiple words like " This is example " and trims spaces from begining and the end of string. – misterGrosar Apr 26 '17 at 07:03
  • 1
    I made a few modifications to angular2-trim-directive (I removed _@HostListener( 'input', [ '$event.target.value' ])_ method and added paste event listener) and it is working like I need it. @Vijay I will accept your comment as an answer. – misterGrosar May 03 '17 at 07:18
  • @misterGrosar what do you mean by 'added paste event listener' ??? In which file you have changed or can you share the code that you have changed ??? – Er Vipin Sharma Jan 06 '18 at 14:22
4

CommonController in example is just base class that triggers subject in onDestroy hook to unsubsribe from observable.

@Directive({
  selector: '[appTrimOnBlur]'
})
export class TrimOnBlurDirective extends CommonController implements OnInit {

  constructor(private elementRef: ElementRef,
              @Self() private ngControl: NgControl) {
    super();
  }

  ngOnInit(): void {
    fromEvent(this.elementRef.nativeElement, 'blur').pipe(
      takeUntil(this.unsubscribeOnDestroy)
    ).subscribe(() => {
      const currentValue: string = this.ngControl.value.toString();
      const whitespace: string = ' ';
      if (currentValue.startsWith(whitespace) || currentValue.endsWith(whitespace)) {
        this.ngControl.control.patchValue(currentValue.trim());
      }
    });
  }

}

You can create generic trim directive, that will make trim not only for blur event,but for any, that you will provide:

@Input() public trimEventName: string = 'blur';

  constructor(private elementRef: ElementRef,
              @Self() private ngControl: NgControl) {
    super();
  }

  ngOnInit(): void {
    fromEvent(this.elementRef.nativeElement, this.trimEventName).pipe(
      takeUntil(this.unsubscribeOnDestroy)
    ).subscribe(() => {
      const currentValue: string = this.ngControl.value.toString();
      const whitespace: string = ' ';
      if (currentValue.startsWith(whitespace) || currentValue.endsWith(whitespace)) {
        this.ngControl.control.patchValue(currentValue.trim());
      }
    });
  }
Artem Krasniuk
  • 1,099
  • 7
  • 6
1

Though it's more than a year late, but you might want to try https://www.npmjs.com/package/ngx-trim-directive

It lies on a simple fact that Angular listens to input event to bring the view-to-model binding into being.

Demo: https://angular-86w6nm.stackblitz.io, editor: https://stackblitz.com/edit/angular-86w6nm

KingMario
  • 161
  • 2
  • 10
1

Following directive could be used with Reactive-Forms to trim all form fields:

@Directive({
  selector: '[formControl], [formControlName]',
})
export class TrimFormFieldsDirective {
  @Input() type: string;

  constructor(@Optional() private formControlDir: FormControlDirective, 
              @Optional() private formControlName: FormControlName) {}

  @HostListener('blur')
  @HostListener('keydown.enter')
  trimValue() {
    const control = this.formControlDir?.control || this.formControlName?.control;
    if (typeof control.value === 'string' && this.type !== 'password') {
      control.setValue(control.value.trim());
    }
  }
}
Vilmantas Baranauskas
  • 6,596
  • 3
  • 38
  • 50
1

I really liked this directive as it just auto-applies to almost everything:

import { Directive, forwardRef, HostListener } from '@angular/core';
import { DefaultValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
const TRIM_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => TrimValueAccessorDirective),
  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({
  // eslint-disable-next-line @angular-eslint/directive-selector
  selector: `
 input:not([type=checkbox]):not([type=radio]):not([type=password]):not([readonly]):not(.ng-trim-ignore)[formControlName],
 input:not([type=checkbox]):not([type=radio]):not([type=password]):not([readonly]):not(.ng-trim-ignore)[formControl],
 input:not([type=checkbox]):not([type=radio]):not([type=password]):not([readonly]):not(.ng-trim-ignore)[ngModel],
 textarea:not([readonly]):not(.ng-trim-ignore)[formControlName],
 textarea:not([readonly]):not(.ng-trim-ignore)[formControl],
 textarea:not([readonly]):not(.ng-trim-ignore)[ngModel],
 :not([readonly]):not(.ng-trim-ignore)[ngDefaultControl]'
 `,
  providers: [TRIM_VALUE_ACCESSOR],
})
export class TrimValueAccessorDirective extends DefaultValueAccessor {
  @HostListener('input', ['$event.target.value'])
  ngOnChange = (val: string) => {
    this.onChange(val.trim());
  };
  @HostListener('blur', ['$event.target.value'])
  applyTrim(val: string) {
    this.writeValue(val.trim());
  }
  writeValue(value: any): void {
    if (typeof value === 'string') {
      value = value.trim();
    }
    super.writeValue(value);
  }
}

From here: https://medium.com/@rm.dev/angular-auto-trim-your-input-string-using-angular-directive-5ae72b8cee9d

Brian Davis
  • 745
  • 1
  • 11
  • 14
0

@ErVipinSharma I changed file src/input-trim.directive.ts, which you can find in above link to the github. In this file I removed method

@HostListener( 'input', ['$event.type', '$event.target.value'] )
onInput( event: string, value: string ): void {
    this.updateValue( event, value );
}

and added method

@HostListener('paste', ['$event', '$event.target'])
onPaste($event: any, target: any) {
    // do something when on paste event happens
}
misterGrosar
  • 294
  • 1
  • 3
  • 10
0

If you use https://github.com/anein/angular2-trim-directive with <input trim="blur" ...> it will allow for middle spaces.

enter image description here

Adam Hughes
  • 14,601
  • 12
  • 83
  • 122
0

You need to use "NgControl" to avoid the problem you have.

You are updating the value of the control with native javascript, but angular is not notified of this change. You need to trigger angular yourself, OR you can use NgControl, like this:

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

@Directive({
    selector: '[appTrimOnBlur]'
})
export class TrimOnBlurDirective {
    constructor(private ngControl: NgControl) { }

    @HostListener('blur')
    onBlur() {
        const trimmedValue = this.ngControl.value?.trim();
        this.ngControl.control.setValue(trimmedValue);
    }
}
Edwin
  • 733
  • 8
  • 20