【问题标题】:Angular template-driven form validation with multiple fields具有多个字段的 Angular 模板驱动的表单验证
【发布时间】:2018-10-26 23:32:42
【问题描述】:

假设我有一个包含一些字段的简单表单 (Stackblitz example):

@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 { ... }

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

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

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

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

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

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

甚至可以在模板驱动的表单中实现吗?

【问题讨论】:

    标签: angular angular-forms angular-validation


    【解决方案1】:

    这就是我设法实现分组验证 (Stackblitz Example) 的方式。

    (对于我在下面粘贴的 sn-ps,括号中的省略号表示为清楚起见省略了部分;完整内容请参阅上面的 stackblitz 示例)

    对于每个组,我添加了一个包装元素(我认为它也可以是 &lt;ng-container&gt;,虽然没有测试它)只是为了承受 ngModelGroup 指令:

    <div ngModelGroup="contacts" (...) >
    
    <h3>--- Contacts ---</h3>
    
    email: (...input...)
    phone: (...input...)
    
    <div ngModelGroup="address" (...) >
    
    <h4>---- Address ----</h4>
    
    street: (...input...)
    city: (...input...)
    zipcode: (...input...)
    
    </div>
    
    </div>
    

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

      ifOneThenFullAddress(c: AbstractControl): ValidationErrors | null {
        let value = c.value;
    
        let street = value && value.street;
        let city = value && value.city;
        let zipcode = value && value.zipcode;
    
        if ((street && city && zipcode) || (!street && !city && !zipcode))
          return null;
    
        return { ifOneThenAll: '' };
      }
    

    (这段代码在UserDataComponent内部实现)

    现在要让 Angular 表单引擎调用我的函数,我必须实现一个 Validator,但这是一个通用的(将验证交给函数):

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

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

    <div ngModelGroup="contacts" [fn-validate]="atLeastOneContact">
    <div ngModelGroup="address" [fn-validate]="ifOneThenFullAddress">
    

    瞧,整个小组都得到了我的临时功能的验证。

    【讨论】:

      【解决方案2】:

      这是可能的,但随着代码变得模糊,这是不切实际的。

      条件一:

      <input [required]=!(somefielf1 || somefield2 || somefield3 ... etc )>
      

      对于每个输入,必须满足不同的条件。如果没有填写任何输入,则需要输入。

      条件二:

      这里相同,只是反过来 - 没有否定

      <input name="city" [required]="zipCode || street || whatever else|| ..."/>
      

      如果提供的字段之一不为空,则需要输入。

      使用响应式表单会更容易做到这一点。条件大致相同,但更易于阅读、维护和更改。

      【讨论】:

      • 这当然可以,但是......每个字段都会标记一个错误(必需的错误)。我宁愿有一个错误“必须提供至少一个联系人”或“您必须输入完整的地址详细信息”。我想知道是否可以将验证代码附加到“ngModelGroup”。
      • 也一样,只是在错误信息上使用*ngIF。
      猜你喜欢
      • 1970-01-01
      • 2017-06-27
      • 1970-01-01
      • 1970-01-01
      • 2018-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-10
      相关资源
      最近更新 更多