具有多个字段的 Angular 模板驱动表单验证

rsl*_*mos 2 angular-validation angular angular-forms

假设我有一个带有一些字段的简单表单(Stackblitz 示例):

@Component({
  selector: 'my-app',
  template: 
`
<h1>AppComponent</h1>

<form>
  <h2>UserData</h2>
  <userdata [user]="model.userData"></userdata>

  <h2>Actions</h2>
  <actionbar ></actionbar>
</form>
`,
})
export class AppComponent  { ... }

@Component({
  selector: 'userdata',
  template: 
`
<span class="status {{name.status}}">{{name.status}}</span>
full name:
<input name="name" #name="ngModel" pattern="^.* .*$" required [(ngModel)]="user.name">
<br>

<h3>--- Contacts ---</h3>

<span class="status {{email.status}}">{{email.status}}</span>
email:
<input name="email" #email="ngModel" type="email" [email]="true" [(ngModel)]="user.contacts.email">
<br>


<span class="status {{phone.status}}">{{phone.status}}</span>
phone:
<input name="phone" #phone="ngModel" pattern="^[0-9]*$" [(ngModel)]="user.contacts.phone">
<br>


<h4>---- Address ----</h4>

<span class="status {{street.status}}">{{street.status}}</span>
street:
<input name="street" #street="ngModel" [(ngModel)]="user.contacts.address.street">
<br>

<span class="status {{city.status}}">{{city.status}}</span>
city:
<input name="city" #city="ngModel" [(ngModel)]="user.contacts.address.city">
<br>

<span class="status {{zipcode.status}}">{{zipcode.status}}</span>
zipcode:
<input name="zipcode" #zipcode="ngModel" pattern="^[0-9]{5}$" [(ngModel)]="user.contacts.address.zipcode">
<br>

`,
  viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
})
export class UserDataComponent  {
  @Input() user: any;
}

@Component({
  selector: 'actionbar',
  template: 
`
<span class="status {{form.status}}">{{form.status}}</span>
<input type="button" value="Submit" [disabled]="form.invalid">
`,
  viewProviders: [{ provide: ControlContainer, useExisting: NgForm }],
})
export class ActionBarComponent { ... }
Run Code Online (Sandbox Code Playgroud)

(基本上是一个包含全名和联系信息字段的表单,例如电子邮件、电话、地址)

请注意,只有在有效的情况下才能提交表格。仅当嵌套在其中的所有内容都有效时,该表单才有效。

对于单个字段的验证,这很容易做到(这些字段已经有一些,如正则表达式模式和必需性)。

现在我想添加另外两个业务需求:

  1. 需要至少一项联系信息(电子邮件、电话或地址);

  2. 如果设置了地址中的任何字段,则所有字段(街道、城市、邮政编码)都是必需的。

甚至可以在模板驱动的表单中做到这一点吗?

rsl*_*mos 5

这就是我成功实现分组验证的方式(Stackblitz 示例)。

\n\n

(对于我在下面粘贴的片段,括号中的省略号表示为了清晰起见,省略了这些部分;有关完整内容,请参阅上面的 stackblitz 示例)

\n\n

对于每个组,我添加了一个包装元素(我认为它也可能是<ng-container>,尽管没有测试它)只是为了承载一个ngModelGroup指令:

\n\n
<div ngModelGroup="contacts" (...) >\n\n<h3>--- Contacts ---</h3>\n\nemail: (...input...)\nphone: (...input...)\n\n<div ngModelGroup="address" (...) >\n\n<h4>---- Address ----</h4>\n\nstreet: (...input...)\ncity: (...input...)\nzipcode: (...input...)\n\n</div>\n\n</div>\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在,每个新的都ngModelGroup可以附加验证器。由于这些验证是如此临时,我觉得它们不值得真正可重用的实现,我所需要的只是验证函数(此处仅粘贴其中一个;另一个非常简单,您可以随时参考堆栈闪电战):

\n\n
  ifOneThenFullAddress(c: AbstractControl): ValidationErrors | null {\n    let value = c.value;\n\n    let street = value && value.street;\n    let city = value && value.city;\n    let zipcode = value && value.zipcode;\n\n    if ((street && city && zipcode) || (!street && !city && !zipcode))\n      return null;\n\n    return { ifOneThenAll: \'\' };\n  }\n
Run Code Online (Sandbox Code Playgroud)\n\n

(这段代码是在里面实现的UserDataComponent

\n\n

现在,为了使角度表单引擎调用我的函数,我必须实现Validator一个通用的函数(它将验证交给函数):

\n\n
@Directive({\n  selector: \'[fn-validate]\',\n  providers: [{provide: NG_VALIDATORS, useExisting: FnValidateDirective, multi: true}]\n})\nexport class FnValidateDirective implements Validator {\n  @Input(\'fn-validate\') fn: (c: AbstractControl) =>  ValidationErrors | null;\n\n  validate(c: AbstractControl): ValidationErrors | null {\n    return this.fn(c);\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

要使用它(并绑定我的验证函数),我必须将组的元素更改为:

\n\n
<div ngModelGroup="contacts" [fn-validate]="atLeastOneContact">\n<div ngModelGroup="address" [fn-validate]="ifOneThenFullAddress">\n
Run Code Online (Sandbox Code Playgroud)\n\n

瞧\xc3\xa0,整个小组得到了我的临时职能的验证。

\n