36
loading...
This website collects cookies to deliver better user experience
Validators
class.Validators
class use the same function under the hood. We can provide multiple validators for an element. What Angular does is stack up the validators in an array and call them one by one.required
, min
, max
, etc. Angular has created directives to match each of these native validation attributes. So when we place these attributes on an input
, Angular can get access to the element and call a validation function whenever the value changes.<input type="email" required minlength [(ngModel)]="email"/>
required
directive looks like:@Directive({
...
providers: [{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => RequiredValidator),
multi: true
}],
...
})
export class RequiredValidator implements Validator {
// .....
validate(control: AbstractControl): ValidationErrors|null {
return this.required ? requiredValidator(control) : null;
}
// .....
}
RequiredValidator
implements an interface called Validator
.Validator
interface forces the class to implement a validate()
method.null
if there is no error.NG_VALIDATORS
Injection token for this.RequiredValidator
class by using the useExisitng
property.required
on a form control, the RequiredValidator
directive gets instantiated and the validator also gets attached to the element. Validators
class exposes a set of static methods that can be used when dealing with Reactive Forms like so:import { FormControl, Validators } from '@angular/forms';
export class AppComponent{
email = new FormControl('', [Validators.required, Validators.minLength(5)]);
}
class Validators {
static min(min: number): ValidatorFn
static max(max: number): ValidatorFn
static required(control: AbstractControl): ValidationErrors | null
static requiredTrue(control: AbstractControl): ValidationErrors | null
static email(control: AbstractControl): ValidationErrors | null
static minLength(minLength: number): ValidatorFn
static maxLength(maxLength: number): ValidatorFn
static pattern(pattern: string | RegExp): ValidatorFn
static nullValidator(control: AbstractControl): ValidationErrors | null
static compose(validators: ValidatorFn[]): ValidatorFn | null
static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn | null
}
export interface ValidatorFn {
(control: AbstractControl): ValidationErrors|null;
}
https
) or not.export const ProtocolValidator: ValidatorFn = (control) => {
const { value } = control;
const isSecure = (value as string).startsWith("https://");
return isSecure ? null : { protocol: `Should be https URI` };
};
ProtocolValidator
to accommodate this change:export const ProtocolValidator = (protocol: string): ValidatorFn => (control) => {
const { value } = control;
const isSecure = (value as string).startsWith(protocol);
return isSecure ? null : { protocol: `Should be ${protocol} URI` };
};
const urlControl = new FormControl('', [Validators.required, ProtocolValidator('https://')]);
<input type="text" [formControl]="urlControl" />
@Directive({
selector: "[protocol]",
providers: [{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ProtocolValidatorDirective),
multi: true
}]
})
export class ProtocolValidatorDirective implements Validator {
@Input() protocol!: string;
validate(control: AbstractControl): ValidationErrors|null {
return ProtocolValidator(this.protocol)(control);
}
}
<input type="text" protocol="wss://" [(ngModel)]="url" />
@Directive({
selector: '[protocol][formControlName],[protocol][formControl],[protocol][ngModel]'
})
protocol
directive is placed should also have either of these attributes:interface AsyncValidatorFn {
(control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>
}
@Injectable({
providedIn: "root"
})
export class UsernameService {
constructor(private http: HttpClient) {}
isUsernameAvailable(username: string) {
return this.http.get("https://jsonplaceholder.typicode.com/users").pipe(
map((users: any[]) => users.map((user) => user?.username?.toLowerCase())),
map(
(existingUsernames: string[]) => !existingUsernames.includes(username)
),
startWith(true),
delay(1000)
);
}
}
@Injectable({
providedIn: "root"
})
export class UsernameValidatorService implements Validator {
constructor(private usernameService: UsernameService) {}
validatorFunction: AsyncValidatorFn = (control) =>
control?.value !== ""
? this.usernameService
.isUsernameAvailable(control.value)
.pipe(
map((isUsernameAvailable) =>
isUsernameAvailable
? null
: { username: "Username not available" }
)
)
: of(null);
validate(control: AbstractControl) {
return this.validatorFunction(control);
}
}
validatorFunction()
? Why can't the logic be placed inside the validate()
method itself?validatorFunction()
when we are using Reactive Forms:export class AppComponent {
constructor(private usernameValidator: UsernameValidatorService) {}
username = new FormControl("", {
asyncValidators: this.usernameValidator.validatorFunction
});
}
@Directive({
selector: "[username][ngModel]",
providers: [
{
provide: NG_ASYNC_VALIDATORS,
useExisting: UsernameValidatorService,
multi: true
}
]
})
export class UsernameValidatorDirective {}
NG_ASYNC_VALIDATORS
instead of NG_VALIDATORS
in this case. And since we already have the UsernameValidatorService
(which implements the Validator
interface).UsernameValidatorService
is providedIn: 'root'
, which means the Injector has the service instance with it. So we just say use that same instance of UsernameValidatorService
by using the useExisitng
property.