好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Angular2 模板驱动表单校验

模板驱动表单,指的是通过html5标准校验的表单,优势在于使用起来简单,但要动态修改验证器、操纵控制器模型不是很方便。

 

Angular2对表单处理做了一系列封装(模板驱动表单以及响应式表单):

数据绑定

这个自然不用说,使用ngModel可以双向绑定到组件里的对象字段。

控件状态检测

Angular会自动根据控件状态加上相应的class,如果我们需要编辑input标签在不同状态下的样式,只需要在css里写相应的类就可以了。

状态 true时的css类 false时的css类
控件是否被访问过 ng-touched ng-untouched
控件值是否已经变化 ng-dirty ng-pristine
控件值是否有效 ng-valid ng-invalid

表单校验

模板驱动表单支持html5标准属性校验:

required:必填 minlength:最小长度 maxlength:最大长度 pattern:正则表达式校验

另外支持自定义Validator.

响应式表单内置了上面四种Validator,也可以自己扩展。

模板驱动表单相关指令封装在FormsModule模块中,app.module.ts里需要先导入:

import {FormsModule} from '@angular/forms' ;

@NgModule({
  declarations: [
    AppComponent,
    ...
  ],
  exports:[AppComponent],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    ...
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { } 

 

FormsModule模块包含NgModule、NgModuleGroup、NgForm和InternalFormsSharedModule模块中包含的指令。

NgForm 标记一个表单 NgModelGroup 字段分组 NgModel 字段

InternalFormsSharedModule是Angular内部模块,FormsModule和ReactiveFormsModule都引用了它,所以可以不用显式引入,直接使用。

下面的例子演示了一个模板驱动表单,包括表单校验、字段分组、控件状态、数据绑定,以及自定义校验器。自定义校验器的功能是校验第二个密码是否与第一个密码相同。

自定义校验器:
repeat-password.directive.ts:

import {Directive, Input, OnChanges, SimpleChanges} from '@angular/core' ;
import {NG_VALIDATORS, FormControl, Validator, AbstractControl, ValidatorFn, NgModel} from  "@angular/forms" ;
  /*  *
 * 自定义指令,用于检验input标签的值是否跟指定input的值标签相同
   */  
@Directive({
  selector:  '[repeatPassword]' ,
  providers: [{provide: NG_VALIDATORS, useExisting: RepeatPasswordDirective, multi:   true  }]
})
export class RepeatPasswordDirective implements Validator,OnChanges{
    /*  *
   * 校验方法
   * @param c
   * @returns {{[p: string]: any}}
     */  
  validate(c: AbstractControl): {[p: string]: any} {
      return  verifyPassword(c, this  .repeatPassword.control);
  }

  ngOnChanges(changes: SimpleChanges):   void   {
        this .repeatPassword=changes['repeatPassword' ].currentValue;
  }

    /*  *
   * 通过属性传入另一个input标签的model
   * 名称与选择器一致,就不需要在使用的时候加额外的属性传入
     */  
  @Input() repeatPassword:NgModel;
  constructor() { }


}
  /*  *
 * 导出校验方法,供响应式表单使用
 * @param password1Controller
 * @returns {(currentControl:AbstractControl)=>{[p: string]: any}}
   */  
export   function   repeatPassword(password1Controller:FormControl):ValidatorFn {
    return  (currentControl: AbstractControl): {[key: string]: any} =>  {
      return   verifyPassword(currentControl,password1Controller);
  };
}

  function   verifyPassword(currentControl: AbstractControl,password1Controller:FormControl):{[key: string]: any} {
      if (! password1Controller.valid) {
      console.log( "密码1无效" );
        return  {password1InValid:{'errorMsg':'' }}
    }
      if ((!currentControl.untouched||currentControl.dirty)&&password1Controller.value!= currentControl.value) {
        return  {passwordNEQ:{'errorMsg':'两次密码输入不一致!' }}
    }
} 

 

创建指令后别忘了在app.module.ts里引入 :

 @NgModule({
  declarations: [
    AppComponent,
    ...,
    RepeatPasswordDirective
  ],
  exports:[AppComponent],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    ...
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { } 

 

当然,如果使用 ng g directive repeatPassword  命令创建指令,会自动添加。

模板:

  <!--(ngSubmit)绑定的表单提交事件,ajax不需要-->
<form #registerForm="ngForm" (ngSubmit)="doSubmit(registerForm.value)" >
  <div>
    <label  for ="userName">用户名:</label>
    <!--给input设置一个本地变量,可以读取errors显示错误信息-->
    <input type="text" >
    <div *ngIf="userName.errors && (userName.dirty || userName.touched)" class="error">
      <span [hidden]="!userName.errors.required">用户名必须输入</span>
      <span [hidden]="!userName.errors.minlength">用户名至少4位</span>
    </div>
  </div>
  <!--ngModelGroup指令可以给表单字段分组,值password是registerForm.value里该组的字段名,#passwordGroup是该组的本地变量名-->
  <fieldset ngModelGroup="passwordGroup" #passwordGroup="ngModelGroup" aria-required="true">
    <label  for ="password1">密码:</label>
    <input type="password" >
    <label  for ="password2">重复密码:</label>
    <!--使用自定义的校验器,加入repeatPassword指令,传入第一个密码输入框的ngModel,即用#password1="ngModel"声明的password1-->
    <input type="password" >
    <span *ngIf="formErrors['passwordGroup.password2']" class="error"> 
      {{ formErrors[ 'passwordGroup.password2'] }} </span>
  </fieldset>

  <div>
    <label  for ="email">邮箱:</label>
    <input type="text" >
    <!-- 可以通过表单的onValueChanged事件,读到当前的错误信息,写到指定字段里 -->
    <div *ngIf="formErrors.email" class="error"> 
      {{ formErrors.email }}
     </div>
  </div>
  <div>
    <label>性别:</label>
    <input type="radio" name="sex" [(ngModel)]="formData.sex" value="male" checked="checked">  男
     <input type="radio" name="sex" [(ngModel)]="formData.sex" value="female" >  女
   </div>
  <fieldset ngModelGroup="nameGroup" #nameGroup="ngModelGroup">
    <label>姓:</label>
    <input type="text" name="firstName" [(ngModel)]="formData.firstName" required><br />
    <label>名:</label>
    <input type="text" name="lastName" [(ngModel)]="formData.lastName">
  </fieldset>

  <button type="button" class="btn btn-default" 
          [disabled] ="!registerForm.valid" (click)="doSubmit(registerForm.value)">注册</button>
</form>
 
{{registerForm.value |json}}

 

组件:

import {Component, OnInit, ViewChild, AfterViewInit} from "@angular/core" ;
import {NgForm} from  "@angular/forms" ;

@Component({
  selector:  'app-form' ,
  templateUrl:  './form.component.html' ,
  styleUrls: [ './form.component.css' ]
})
export class FormComponent implements OnInit,AfterViewInit {
  ngAfterViewInit():   void   {
      //  订阅表单值改变事件 
     this .registerForm.valueChanges.subscribe(data =>  this  .onValueChanged(data));
  }
    //  找到表单 
  @ViewChild('registerForm' ) registerForm: NgForm;


  constructor() {
  }

  formData  =  {} as any;
  ngOnInit() {
      //  默认性别为male 
     this .formData.sex = "male" ;

  }
  doSubmit(obj: any) {
      //  表单提交 
     console.log(JSON.stringify(obj));
  }

  onValueChanged(data) {

      for  (const field  in   this  .formErrors) {
        this .formErrors[field] = '' ;
        //  取到表单字段 
      const control =  this  .registerForm.form.get(field);
        //  表单字段已修改或无效 
       if  (control && control.dirty && ! control.valid) {
          //  取出对应字段可能的错误信息 
        const messages =  this  .validationMessages[field];
          //  从errors里取出错误类型,再拼上该错误对应的信息 
         for  (const key  in   control.errors) {
            this .formErrors[field] += messages[key] + '' ;
        }
      }

    }

  }


    //  存储错误信息 
  formErrors =  {
     'email': '' ,
     'userName': '' ,
     'passwordGroup.password1':'' ,
     'passwordGroup.password2':'' ,
     'sex':'' 
  };
    //  错误对应的提示 
  validationMessages =  {
     'email' : {
       'required': '邮箱必须填写.' ,
       'pattern': '邮箱格式不对' ,
    },
     'userName' : {
       'required': '用户名必填.' ,
       'minlength': '用户名太短' ,
    },
     'passwordGroup.password1' :{
       'required': '请输入密码' ,
       'minlength': '密码太短' ,
    },
     'passwordGroup.password2' :{
       'required': '请重复输入密码' ,
       'minlength': '密码太短' ,
       'passwordNEQ':'两次输入密码不同' ,
       'password1InValid':'' 
    },
     'sex' :{
       'required':'性别必填' 
    }

  };
} 

 

查看更多关于Angular2 模板驱动表单校验的详细内容...

  阅读:35次