Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

model values not trim automatically in angular 2

I am using angular 2 form validation, I set required validation on text box, when I enter nothing into textbox it shows required error message it is ok but when I enter only spaces then it does not show required error message, which means angular 2 not trim the model values.

In angular 1.x it automatically trims the model values but in angular 2 I don't see this feature.

like image 530
amol Avatar asked May 06 '16 07:05

amol


8 Answers

Following directive could be used with Reactive-Forms to trim all form fields without explicitly specifying it anywhere:

@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());
    }
  }
}
like image 101
Vilmantas Baranauskas Avatar answered Oct 10 '22 14:10

Vilmantas Baranauskas


I think that you need to implement a custom value accessor for this. Something like that:

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

@Directive({
  selector: 'input[trim]',
  host: { '(keyup)': 'doOnChange($event.target)' },
  providers: [ TRIM_VALUE_ACCESSOR ]
})
export class TrimValueAccessor extends DefaultValueAccessor {
  onChange = (_) => {};
  onTouched = () => {};

  constructor(private renderer:Renderer) {
  }

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

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

When you want to use this value accessor, add the corresponding directive into your component and the trim attribute on your input:

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

See this article for more details (section "NgModel-compatible component"):

  • http://restlet.com/blog/2016/02/17/implementing-angular2-forms-beyond-basics-part-2/
like image 28
Thierry Templier Avatar answered Oct 10 '22 14:10

Thierry Templier


Well, there is a long discussion on github with the following result: we must implement our own validators.

This is what I use so far:

import { ValidatorFn, AsyncValidatorFn, Validators as V, FormControl } from '@angular/forms';

// the need in this validators is the non-trimming angular standard behavior
// see https://github.com/angular/angular/issues/8503
export class Validators {

  public static required(control: FormControl) {
    if (!control.value || typeof control.value === 'string' && !control.value.trim()) {
      return {
        required: true
      };
    }

    return null;
  }

  public static minLength(length: number): ValidatorFn {
    return (control: FormControl) => {
      if (!control.value || typeof control.value === 'string' && control.value.trim().length < length) {
        return {
          minlength: true
        };
      }

      return null;
    };
  }

  public static maxLength(length: number): ValidatorFn {
    return (control: FormControl) => {
      if (control.value && typeof control.value === 'string' && control.value.trim().length > length) {
        return {
          maxlength: true
        };
      }

      return null;
    };
  }

  public static pattern(pattern: string): ValidatorFn {
    return V.pattern(pattern);
  }

  public static minAmount(amount: number): ValidatorFn {
    return (control: FormControl) => {
      if (control.value && control.value.length < amount) {
        return {
          minamount: true
        };
      }

      return null;
    };
  }

  public static maxAmount(amount: number): ValidatorFn {
    return (control: FormControl) => {
      if (control.value && control.value.length > amount) {
        return {
          maxamount: true
        };
      }

      return null;
    };
  }

  public static compose(validators: ValidatorFn[]): ValidatorFn {
    return V.compose(validators);
  }

  public static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn {
    return V.composeAsync(validators);
  }

};

This mimics the standard maxLength, minLength and required validations for string input but with trimming and proxies other functions to the standard ones.

To use it just import your Validators instead of @angular/forms one, e.g.:

import { FormControl } from '@angular/forms';
import { Validators } from 'path/to/validators';

...
let control = new FormControl('', Validators.compose(
  Validators.required, Validators.minLength(6)
));
...

This maybe does not trim the model but it solves the validation problem specified in the request.

like image 37
smnbbrv Avatar answered Oct 10 '22 14:10

smnbbrv


Just my two cents:

  • https://www.npmjs.com/package/ng2-trim-on-blur

  • https://www.npmjs.com/package/ngx-trim-directive

Both directives lie on a simple fact that Angular listens to input event to bring the view-to-model binding into being (not for cases of blur or submit updateOn options. These cases are also handled).

The second one also deals with the caret position if the trim is triggered by input event.

These directives leave the states of dirty and touched to be handled by the original Angular ValueAccessor, so you won't meet with some strange behaviors.

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

like image 44
KingMario Avatar answered Oct 10 '22 13:10

KingMario


Another solution might be to put the trim logic inside a FormControl wrapper. This approach is simple, doesn't require third party dependencies and works with default validators such as required or minLength.

export class TrimFormControl extends FormControl {
    private _value!: string | null;

    get value(): string | null {
        return this._value;
    }

    set value(value: string | null) {
        this._value = value ? value.trim() : value;
    }
}
like image 20
Valeriy Katkov Avatar answered Oct 10 '22 14:10

Valeriy Katkov


You can simply install the TrimValueAccessor npm package that I created:

npm install ng-trim-value-accessor --save

and then import it into your application:

import { NgModule } from '@angular/core';
import { TrimValueAccessorModule } from 'ng-trim-value-accessor';

@NgModule({
  imports: [
    TrimValueAccessorModule
  ]
})
export class AppModule { }
like image 45
Khashayar Avatar answered Oct 10 '22 13:10

Khashayar


Here is the code for Angular4 or install it from npm. https://www.npmjs.com/package/ngx-trim

const TRIM_VALUE_ACCESSOR = {
  provide: NG_VALUE_ACCESSOR,
  // tslint:disable-next-line:no-forward-ref
  useExisting: forwardRef(() => NgxTrimDirective),
  multi: true
};

@Directive({
  selector: 'input[ngxTrim]',
  providers: [TRIM_VALUE_ACCESSOR]
})
export class NgxTrimDirective implements ControlValueAccessor {
  _onChange(_: any) { }
  _onTouched() { }
  registerOnChange(fn: (value: any) => any): void { this._onChange = fn; }
  registerOnTouched(fn: () => any): void { this._onTouched = fn; }
  constructor (
    private _renderer: Renderer2,
    private _elementRef: ElementRef
  ) { }

  writeValue(value: any): void {
    value && this._renderer.setProperty(this._elementRef.nativeElement, 'value', value);
  }

  @HostListener('keyup', ['$event.target'])
  _onKeyUp(element: HTMLInputElement) {
    let val = element.value.trim();
    this.writeValue(val);
    this._onChange(val);
  }
}

BTW, I think it should implement ControlValueAccessor instead of extending DefaultValueAccessor.

like image 25
maxisam Avatar answered Oct 10 '22 14:10

maxisam


I had a similar issue. Users would copy and paste string information from a text file or email and occasionally capture an extra space or two. When they would paste that into the field, the validators would fail and present an error that could occasionally confuse the user. I needed to trim the values on paste to eliminate this confusion. Solutions I looked at were the various custom value accessor libraries but I didn't want to go importing a library for a situation I needed to solve in one or two places. Thankfully, Angular provides some pretty easy options for doing exactly this in a few short lines.

I created a simple function that checks the supplied value. If the supplied value is not null and the supplied value is different after applying a trim, then we patch the value of the supplied control and trigger the update and validate cycle.

const TrimValue = (v: string | null, control: AbstractControl ) => {
  if (v && v !== v.trim()) {
    control.patchValue(v.trim());
    control.updateValueAndValidity();
  }
};

With that function available, all I needed to do was subscribe to the valueChanges emitter on the control and call TrimValue passing the value change emitted and a reference to the control itself.

this.Form = new FormGroup (
  trimMe: new FormControl('', [Validators.required, Validators.pattern(myRegex)])
);
this.Form.get('trimMe').valueChanges.subscribe(
  (val) => TrimValue(val, this.Form.get('trimMe'))
);

I took things a little farther and added a reference to the valueChanges subscription to an array of subscriptions for the component. Then in ngOnDestroy, I iterate over the subscription array and unsubscribe to clean up my messes when the component is done.

Anyway, the upside to this solution is it applies the trim, doesn't interfere with the validators associated with the control and my users can now paste with confidence and without seeing errors because there was a space or two. It also doesn't require writing a custom value accessor decorator / directive or importing an additional library to solve an issue that may only be a concern for a few input fields in your application.

like image 26
ldapmonkey Avatar answered Oct 10 '22 13:10

ldapmonkey