使用模板驱动验证需要依赖于原生的HTML表单验证器 Angular 会用指令来匹配具有验证功能的这些属性。
原生的HTMl验证器主要分两种
Input type Constraint description Associated violation The value must be an absolute URL, as defined in the URL Living Standard. TypeMismatch constraint violation The value must be a syntactically valid email address, which generally has the format username@hostname.tld
.TypeMismatch constraint violation
Attribute Input types supporting the attribute Possible values Constraint description Associated violation pattern
text
,search
,url
,tel
,password
A JavaScript regular expression (compiled with the ECMAScript 5 global
,ignoreCase
, andmultiline
flags disabled)The value must match the pattern. patternMismatch
constraint violationmin
range
,number
A valid number The value must be greater than or equal to the value. rangeUnderflow
constraint violationdate
,month
,week
A valid date datetime
,datetime-local
,time
A valid date and time max
range
,number
A valid number The value must be less than or equal to the value rangeOverflow
constraint violationdate
,month
,week
A valid date datetime
,datetime-local
,time
A valid date and time required
text
,search
,url
,tel
,password
,date
,datetime
,datetime-local
,month
,week
,time
,number
,checkbox
,radio
,file
; also on theand
elements
none as it is a Boolean attribute: its presence means true, its absence means false There must be a value (if set). valueMissing
constraint violationstep
date
An integer number of days Unless the step is set to the any
literal, the value must be min + an integral multiple of the step.stepMismatch
constraint violationmonth
An integer number of months week
An integer number of weeks datetime
,datetime-local
,time
An integer number of seconds range
,number
An integer minlength
text
,search
,url
,tel
,password
; also on theelement
An integer length The number of characters (code points) must not be less than the value of the attribute, if non-empty. All newlines are normalized to a single character (as opposed to CRLF pairs) for .
tooShort
constraint violationmaxlength
text
,search
,url
,tel
,password
; also on theelement
An integer length The number of characters (code points) must not exceed the value of the attribute. tooLong
constraint violation
每当表单控件中的值发生变化时,Angular就会进行验证,并生成一个验证错误的列表(对应着INVALID状态)或者null(对应着VALID状态);
可以通过吧ngModel导出成局部模板变量来查看控件的状态,比如像下面这样
Name is required.
Name must be at least 4 characters long.
Name cannot be Bob.
#name="ngModel"
把 NgModel
导出成了一个名叫 name
的局部变量。NgModel
把自己控制的 FormControl
实例的属性映射出去,让你能在模板中检查控件的状态,比如 valid
和 dirty
。要了解完整的控件属性,参见 API 参考手册中的AbstractControl。
响应式表单控制的源头在组件类,就不能通过模板上的属性来添加验证器了,而是直接在组件类中直接把验证器函数添加到表单控件模型(FormControl)上。当控件发生变化的时候就会调用这些函数。
ngOnInit(): void {
this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
]),
'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required)
});
}
Name is required.
Name must be at least 4 characters long.
Name cannot be Bob.
name
读取器。required
属性仍然存在,虽然验证不再需要它,但你仍然要在模板中保留它,以支持 CSS 样式或可访问性。 从验证过程上看有两种验证器函数:同步验证器和异步验证器。
出于性能方面的考虑,只有在所有同步验证器都通过之后,Angular 才会运行异步验证器。当每一个异步验证器都执行完之后,才会设置这些验证错误。
从验证器来源来看也有两种验证器:内置验证器和自定义验证器
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
}
// 声明成指令给模板验证用 shared/forbidden-name.directive.ts (directive)
@Directive({
selector: '[appForbiddenName]',
providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
})
export class ForbiddenValidatorDirective implements Validator {
@Input('appForbiddenName') forbiddenName: string;
validate(control: AbstractControl): {[key: string]: any} | null {
return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control) : null;
}
}
// 定义函数给验证方法用
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} | null => {
const forbidden = nameRe.test(control.value);
return forbidden ? {'forbiddenName': {value: control.value}} : null;
};
}
这个函数实际上是一个工厂,它接受一个用来检测指定名字是否已被禁用的正则表达式,并返回一个验证器函数。
forbiddenNameValidator
工厂函数返回配置好的验证器函数。 该函数接受一个 Angular 控制器对象,并在控制器值有效时返回 null,或无效时返回验证错误对象。 验证错误对象通常有一个名为验证秘钥(forbiddenName
)的属性。其值为一个任意词典,你可以用来插入错误信息({name}
)。
自定义异步验证器和同步验证器很像,只是它们必须返回一个稍后会输出 null 或“验证错误对象”的承诺(Promise)或可观察对象,如果是可观察对象,那么它必须在某个时间点被完成(complete),那时候这个表单就会使用它输出的最后一个值作为验证结果。(译注:HTTP 服务是自动完成的,但是某些自定义的可观察对象可能需要手动调用 complete 方法)
Angular 会自动把很多控件属性作为 CSS 类映射到控件所在的元素上。你可以使用这些类来根据表单状态给表单控件元素添加样式。目前支持下列类:
|
.ng-valid[required], .ng-valid.required {
|
除了单独的控件验证之外,有时候还需要多控件的联合验证,这个时候就需要用到跨字段的交叉验证方式。先粘代码
ValidatorFn
接口。它接收一个 Angular 表单控件对象作为参数,当表单有效时,它返回一个 null,否则返回 ValidationErrors
对象。FormGroup
的 get 方法来获取子控件。然后,简单地比较一下 name
和 alterEgo
控件的值。// 先定义指令给模板验证使用,指令调用导出的验证方法
@Directive({
selector: '[appIdentityRevealed]',
providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, multi: true }]
})
export class IdentityRevealedValidatorDirective implements Validator {
validate(control: AbstractControl): ValidationErrors {
return identityRevealedValidator(control)
}
}
// 下面的方法给指令使用也可以给组件类使用
export const identityRevealedValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
const name = control.get('name');
const alterEgo = control.get('alterEgo');
return name && alterEgo && name.value === alterEgo.value ? { 'identityRevealed': true } : null;
};
响应式表单验证:需要在创建FormGroup时把一个新的验证器传给他的第二个参数.
const heroForm = new FormGroup({
'name': new FormControl(),
'alterEgo': new FormControl(),
'power': new FormControl()
}, { validators: identityRevealedValidator });
模板驱动表单验证:我们要把该指令添加到 HTML 模板中。由于验证器必须注册在表单的最高层,所以我们要把该指令放在 form
标签上。
就像同步验证器有 ValidatorFn
和 Validator
接口一样,异步验证器也有自己对应的接口:AsyncValidatorFn
和 AsyncValidator
。
它们非常像,但是有下列不同:
它们必须返回承诺(Promise)或可观察对象(Observable),
返回的可观察对象必须是有限的,也就是说,它必须在某个时间点结束(complete)。要把无尽的可观察对象转换成有限的,可以使用 first
、last
、take
或 takeUntil
等过滤型管道对其进行处理。
注意!异步验证总是会在同步验证之后执行,并且只有当同步验证成功了之后才会执行。如果更基本的验证方法已经失败了,那么这能让表单避免进行可能会很昂贵的异步验证过程,比如 HTTP 请求。
在异步验证器开始之后,表单控件会进入 pending
状态。你可以监视该控件的 pending
属性,利用它来给用户一些视觉反馈,表明正在进行验证。
常见的 UI 处理模式是在执行异步验证时显示一个旋转指示标(spinner)。下面的例子展示了在模板驱动表单中该怎么做:
@Injectable({ providedIn: 'root' })
export class UniqueAlterEgoValidator implements AsyncValidator {
constructor(private heroesService: HeroesService) {}
validate(
ctrl: AbstractControl
): Promise | Observable {
return this.heroesService.isAlterEgoTaken(ctrl.value).pipe(
map(isTaken => (isTaken ? { uniqueAlterEgo: true } : null)),
catchError(() => of(null))
);
}
}
// HeroesService 负责向英雄数据库发起一个 HTTP 请求
interface HeroesService {
isAlterEgoTaken: (alterEgo: string) => Observable;
}
当验证开始的时候,UniqueAlterEgoValidator
把任务委托给 HeroesService
的 isAlterEgoTaken()
方法,并传入当前控件的值。这时候,该控件会被标记为 pending
状态,直到 validate()
方法所返回的可观察对象完成(complete)了。
isAlterEgoTaken()
方法会发出一个 HTTP 请求,并返回一个 Observable
型结果。我们通过 map
操作符把响应对象串起来,并把它转换成一个有效性结果。 与往常一样,如果表单有效则返回 null
,否则返回 ValidationErrors
。我们还是用 catchError
操作符来确保对任何潜在错误都进行了处理。
这里,我们决定将 isAlterEgoTaken()
中的错误视为成功验证。你也可以将其视为失败,并返回 ValidationError
对象。
一段时间之后,可观察对象完成了,异步验证也就结束了。这时候 pending
标志就改成了 false
,并且表单的有效性也更新了。
默认情况下,每当表单值变化之后,都会执行所有验证器。对于同步验证器,没有什么会显著影响应用性能的地方。不过,异步验证器通常会执行某种 HTTP 请求来对控件进行验证。如果在每次按键之后都发出 HTTP 请求会给后端 API 带来沉重的负担,应该尽量避免。
我们可以把 updateOn
属性从 change
(默认值)改成 submit
或 blur
来推迟表单验证的更新时机。
对于模板驱动表单:
对于响应式表单:
new FormControl('', {updateOn: 'blur'});