如果我们仔细观察上面的输出的话,会发现一个问题: user 中是有一个嵌套对象 address 的,而表单中没有嵌套对象的。如果要实现表单中的结构和领域对象的结构一致的话,我们就得请出 ngModelGroup 了。 ngModelGroup 会创建并绑定一个 FormGroup 到该 DOM 元素。 FormGroup 又是什么呢?简单来说,是一组 FormControl。
<!-- 使用 ngModelGroup 来创建并绑定 FormGroup --> <div ngModelGroup="address"> <label> <span>省份</span> <select (change)="onProvinceChange()" [(ngModel)]="user.address.province"> <option value="">请选择省份</option> <option [value]="province" *ngFor="let province of provinces">{{province}}</option> </select> </label> <!-- 省略其他部分 --> </div>
这样的话,我们再来看一下输出,现在就完全一致了:
表单和领域对象的结构也完全一致了
数据验证
模版驱动型的表单的验证也是主要由模版来处理的,在看怎么使用之前,需要界定一下验证规则:
三个必填项: email , password 和 repeat
email 的形式需要符合电子邮件的标准
password 和 repeat 必须一致
当然除了这几个规则,我们还希望在表单未验证通过时提交按钮是不可用的。
<form novalidate #f="ngForm"> <label> <span>电子邮件地址</span> <input type="text" placeholder="请输入您的 email 地址" [ngModel]="user.email" required pattern="([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+.[a-zA-Z]{2,4}"> </label> <div> <label> <span>密码</span> <input type="password" placeholder="请输入您的密码" [(ngModel)]="user.password" required minlength="8"> </label> <label> <span>确认密码</span> <input type="password" placeholder="请再次输入密码" [(ngModel)]="user.repeat" required minlength="8"> </label> </div> <!-- 省略其他部分 --> <button type="submit" [disabled]="f.invalid">注册</button> </form> <div>
Angular 中有几种内建支持的验证器( Validators )
required - 需要 FormControl 有非空值
minlength - 需要 FormControl 有最小长度的值
maxlength - 需要 FormControl 有最大长度的值
pattern - 需要 FormControl 的值可以匹配正则表达式
如果我们想看到结果的话,我们可以在模版中加上下面的代码,将错误以 JSON 形式输出即可。
<div> <span>email 验证:</span> {{f.controls.email?.errors | json}} </div>
我们看到,如果不填电子邮件的话,错误的 JSON 是 {"required": true} ,这告诉我们目前有一个 required 的规则没有被满足。
验证结果
当我们输入一个字母 w 之后,就会发现错误变成了下面的样子。这是因为我们对于 email 应用了多个规则,当必填项满足后,系统会继续检查其他验证结果。
{ "pattern": { "requiredPattern": "^([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+.[a-zA-Z]{2,4}$", "actualValue": "w" } }
通过几次实验,我们应该可以得出结论,当验证未通过时,验证器返回的是一个对象, key 为验证的规则(比如 required, minlength 等),value 为验证结果。如果验证通过,返回的是一个 null 。
知道这一点后,我们其实就可以做出验证出错的提示了,为了方便引用,我们还是导出 ngModel 到一个 email 引用,然后就可以访问这个 FormControl 的各个属性了:验证的状态( valid/invalid )、控件的状态(是否获得过焦点 -- touched/untouched,是否更改过内容 -- pristine/dirty 等)
<label> <span>电子邮件地址</span> <input ... [ngModel]="user.email" #email="ngModel"> </label> <div *ngIf="email.errors?.required && email.touched"> email 是必填项 </div> <div *ngIf="email.errors?.pattern && email.touched"> email 格式不正确 </div>
自定义验证
内建的验证器对于两个密码比较的这种验证是不够的,那么这就需要我们自己定义一个验证器。对于响应式表单来说,会比较简单一些,但对于模版驱动的表单,这需要我们实现一个指令来使这个验证器更通用和更一致。因为我们希望实现的样子应该是和 required 、 minlength 等差不多的形式,比如下面这个样子 validateEqual="repeat"