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.
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());
}
}
}
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"):
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.
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
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;
}
}
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 { }
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With