精华内容
下载资源
问答
  • Angular响应式表单

    2019-07-22 22:44:55
    Angular响应式表单 Angular响应式表单是建立在RxJS基础上,有很多接口是使用RxJs接口实现 一.响应式表单基础元件 1.项目引入 在@angular/forms中引入ReactiveFormsModule 响应式表单基础元件: FormControl [来自@...

    Angular响应式表单

    Angular响应式表单是建立在RxJS基础上,有很多接口是使用RxJs接口实现

    一.响应式表单基础元件

    1.项目引入

    • @angular/forms中引入ReactiveFormsModule
    • 响应式表单基础元件: FormControl [来自@angular/forms]

    2.项目构建

    • 创建FormControl对象

      import { Component } from '@angular/core';
      import { FormControl } from '@angular/forms';
      
      @Component({
        selector: 'app-root',
        templateUrl: './app.component.html',
        styleUrls: ['./app.component.css']
      })
      export class AppComponent {
        user = new FormControl();
      }
      
    • 使用定义的对象

      <input type="text" [formControl]="user" />
      <div>
          {{user.value | json}}
      </div>
      
    • 效果:当在输入框输入内容时,会直接在页面中进行显示

    3.组件中获取和更新控件的值

    • 可以通过监听FormControl的valueChanges方法获取对应值,也可以通过setValue方法设置对应值

      import { Component, OnInit } from '@angular/core';
      import { FormControl } from '@angular/forms';
      
      @Component({
        selector: 'app-root',
        templateUrl: './app.component.html',
        styleUrls: ['./app.component.css']
      })
      export class AppComponent implements OnInit{
        user = new FormControl();
        ngOnInit() {
          // 由于FormControl的valueChanges方法返回的是Observable对象
          // 需要使用subscribe的方式进行监听
          this.user.valueChanges.subscribe((val)=>{
            console.log(val);
          });
        }
        changeUserName() {
          this.user.setValue('Jack'); //通过FormControl的setValue方法可以设置对应的值
        }
      }
      
    • 在模板中进行显示

      <input type="text" [formControl]="user"/>
      <button (click)="changeUserName()">Click</button>
      <div>
        {{user.value | json}}
      </div>
      

    二.使用FormGroup创建表单

    1.项目引入

    • @angular/forms中引入FormGroup和FormControl

    2.实例代码

    • ts中定义FormGroup对象,并包含多个FormControl对象;声明onSubmit方法用于打印FormGroup中的数据信息

      import { Component, OnInit } from '@angular/core';
      import { FormGroup, FormControl } from '@angular/forms';
      @Component({
        selector: 'app-FormGroupTest',
        templateUrl: './FormGroupTest.component.html',
        styleUrls: ['./FormGroupTest.component.css']
      })
      export class FormGroupTestComponent {
        userInfo = new FormGroup({
          userName: new FormControl(),
          phone: new FormControl(),
          sex: new FormControl()
        });
        onSubmit() {
          console.log(this.userInfo.value);
        }
      }
      
    • html中使用[formGroup]绑定ts中的对象,通过angular自带的form标签的提交事件ngSubmit设置点击submit按钮时的触发事件

      <form [formGroup]="userInfo" (ngSubmit)="onSubmit()">
        <input type="text" formControlName="userName"/>
        <input type="text" formControlName="phone"/>
        
        <input type="radio" formControlName="sex" value="1"/><input type="radio" formControlName="sex" value="0"/><button type="submit">提交</button>
      </form>
      
    • 此时点击提交后打印的内容就是表单中实际值

    3.对表单字段进行分组

    • 如果想声明嵌套表单,则可以通过FormGroup中嵌套FormGroup的方式进行实现

    • TypeScript中实现内容

      import { Component, OnInit } from '@angular/core';
      import { FormGroup, FormControl } from '@angular/forms';
      
      @Component({
        selector: 'app-FormGroupTest',
        templateUrl: './FormGroupTest.component.html',
        styleUrls: ['./FormGroupTest.component.css']
      })
      export class FormGroupTestComponent {
        userInfo = new FormGroup({
          userName: new FormControl(),
          phone: new FormControl(),
          sex: new FormControl(),
          pwd: new FormGroup({
            password: new FormControl(),
            confirmPassword: new FormControl()
          })
        });
        onSubmit() {
          console.log(this.userInfo.value);
        }
      }
      
    • Html中在form中添加form并设置属性名为formGroupName

      <form [formGroup]="userInfo" (ngSubmit)="onSubmit()">
        <input type="text" formControlName="userName"/>
        <input type="text" formControlName="phone"/>
        
        <input type="radio" formControlName="sex" value="1"/><input type="radio" formControlName="sex" value="0"/><form formGroupName="pwd">
          <input type="text" formControlName="password"/>
          <input type="text" formControlName="confirmPassword"/>
        </form>
      
        <button type="submit">提交</button>
      </form>
      
    • 输出内容为嵌套对象的形式

      {
          phone: "180411",
          pwd: {password: "123456", confirmPassword: "123456"},
          sex: "1",
          userName: "jack"
      }
      

    4.更新表单中字段

    • 注意:如果使用FormGroup的方式声明表单,再使用form.setValue()方法时需要设置所有表单值,如设置不全则会进行报错

    • 更新表单指定字段方式一:使用form.patchValue(键值对象);方法设置表单中指定字段的值

    • 更新表单指定字段方式二:先使用form.get(键)获取formControl对象,再调用setValue方法:,如this.userInfo.get('userName').setValue('Jerry');

    • 实例代码

      • TypeScript

        import { Component, OnInit } from '@angular/core';
        import { FormGroup, FormControl } from '@angular/forms';
        @Component({
          selector: 'app-FormGroupTest',
          templateUrl: './FormGroupTest.component.html',
          styleUrls: ['./FormGroupTest.component.css']
        })
        export class FormGroupTestComponent {
          userInfo = new FormGroup({
            userName: new FormControl(),
            phone: new FormControl(),
            sex: new FormControl(),
            pwd: new FormGroup({
              password: new FormControl(),
              confirmPassword: new FormControl()
            })
          });
          onSubmit() {
            console.log(this.userInfo.value);
          }
          updateFormData() {
            // this.userInfo.patchValue({'userName':'Jack'});
            this.userInfo.get('userName').setValue('Jerry');
          }
        }
        
      • HTML

        <form [formGroup]="userInfo" (ngSubmit)="onSubmit()">
          <input type="text" formControlName="userName"/>
          <input type="text" formControlName="phone"/>
          
          <input type="radio" formControlName="sex" value="1"/><input type="radio" formControlName="sex" value="0"/><form formGroupName="pwd">
            <input type="text" formControlName="password"/>
            <input type="text" formControlName="confirmPassword"/>
          </form>
        
          <button type="submit">提交</button>
          <button (click)="updateFormData()">更新表单</button>
        </form>
        

    5.使用内置验证器验证表单字段

    • 可以通过给FormControl构造函数的第一个参数设置表单字段的默认值,第二个参数设置验证器类型如required、最小最大长等

    • 通过声明get方法的方式指定对应get的属性值,方便在html中直接调用

    • 通过FromControl的invalid的方法可以判断表单字段是否通过,touched和dirty方法指定当触发之后再回执行判断,hasError方法设置判断对应类型的错误

    • TypeScript代码

      import { Component, OnInit } from '@angular/core';
      import { FormGroup, FormControl, Validators, AbstractControl } from '@angular/forms';
      
      @Component({
        selector: 'app-FormGroupTest',
        templateUrl: './FormGroupTest.component.html',
        styleUrls: ['./FormGroupTest.component.css']
      })
      export class FormGroupTestComponent {
        userInfo = new FormGroup({
          // 参数1: 设置默认值; 参数2:设置校验器
          userName: new FormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(5)]),
          phone: new FormControl('', [Validators.required, Validators.pattern(/^1[34578]\d{9}$/)]),
          sex: new FormControl('1', [Validators.required]),
          pwd: new FormGroup({
            password: new FormControl(),
            confirmPassword: new FormControl()
          })
        });
        onSubmit() {
          console.log(this.userInfo.value);
        }
        updateFormData() {
          // this.userInfo.patchValue({'userName':'Jack'});
          this.userInfo.get('userName').setValue('Jerry');
        }
        // 定义get方法直接获取属性值
        get userName(): AbstractControl {
          return this.userInfo.get('userName');
        }
        get phone(): AbstractControl {
          return this.userInfo.get('phone');
        }
      }
      
    • HTML代码

      <form [formGroup]="userInfo" (ngSubmit)="onSubmit()">
        <fieldset>
          <label>账号:
            <input type="text" formControlName="userName"/>
          </label>
          <!-- 判断如果用户名的表单无效且是在被点击或dirty状态时显示错误内容 -->
          <span *ngIf="userName.invalid && (userName.touched || userName.dirty)">
            <span *ngIf="userName.hasError('required')">请输入用户名</span>
            <span *ngIf="userName.hasError('minlength') || userName.hasError('maxlength')">请输入3-5位用户名</span>
          </span>
        </fieldset>
        <fieldset>
          <label>手机号:
            <input type="text" formControlName="phone"/>
          </label>
          <span *ngIf="phone.invalid && (phone.touched || phone.dirty)">
            请输入手机号
          </span>
        </fieldset>
        <fieldset>
          <label>性别:
            <input type="radio" formControlName="sex" value="1"/><input type="radio" formControlName="sex" value="0"/></label>
        </fieldset>
        <fieldset>
          <form formGroupName="pwd">
            <label>密码:
              <input type="text" formControlName="password"/>
            </label>
            <label>确认密码:
              <input type="text" formControlName="confirmPassword"/>
            </label>
          </form>
        </fieldset>
        <button type="submit">提交</button>
        <button (click)="updateFormData()">更新表单</button>
      </form>
      

    6.提示信息的优化处理

    • 可以使用表单字段的error对象获取对应的错误信息内容。当校验都通过时返回null,required条件没通过时返回{'required':true},当length条件没通过时返回{'maxlength':{'requiredLength':5,'actualLength':实际字段长度}}

    • 可以通过修改FormControl的第二个参数值为AbstractControlOptions类型定义对应的校验规则,在对象中可以通过validators设置校验器类型,通过updateOn设置什么时间触发校验

    • TypeScript代码

      import { Component, OnInit } from '@angular/core';
      import { FormGroup, FormControl, Validators, AbstractControl } from '@angular/forms';
      
      @Component({
        selector: 'app-FormGroupTest',
        templateUrl: './FormGroupTest.component.html',
        styleUrls: ['./FormGroupTest.component.css']
      })
      export class FormGroupTestComponent {
        userInfo = new FormGroup({
          userName: new FormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(5)]),
          // 可以设置第二个参数为一个AbstractControlOptions类型
          // 在该对象类型中可以传递validators / asyncValidators / updateOn
          // 其中updateOn表示当什么时间触发校验
          phone: new FormControl('', {validators: [Validators.required, Validators.pattern(/^1[34578]\d{9}$/)], updateOn:'blur'}),
          sex: new FormControl('1', [Validators.required]),
          pwd: new FormGroup({
            password: new FormControl(),
            confirmPassword: new FormControl()
          })
        });
        onSubmit() {
          console.log(this.userInfo.value);
        }
        updateFormData() {
          // this.userInfo.patchValue({'userName':'Jack'});
          this.userInfo.get('userName').setValue('Jerry');
        }
        get userName(): AbstractControl {
          return this.userInfo.get('userName');
        }
        get phone(): AbstractControl {
          return this.userInfo.get('phone');
        }
      }
      
    • HTML代码

      <form [formGroup]="userInfo" (ngSubmit)="onSubmit()">
        <fieldset>
          <label>账号:
            <input type="text" formControlName="userName"/>
          </label>
          <!-- 判断如果用户名的表单无效且是在被点击或dirty状态时显示错误内容 -->
          <span *ngIf="userName.invalid && (userName.touched || userName.dirty)">
            <!-- 使用errors对象可以获取到对应的错误内容 -->
            <span *ngIf="userName.errors.required">请输入用户名</span>
            <span *ngIf="userName.errors.minlength">用户名的长度不能小于{{userName.errors.minlength.requiredLength}}个字符</span>
            <span *ngIf="userName.errors.maxlength">用户名的长度不能超过{{userName.errors.maxlength.requiredLength}}个字符</span>
            <!--
              检验都通过返回null
              当required条件没通过时返回的是: {'required': true} 
              当length条件没通过时返回的是: {'maxlength': {'requiredLength':5, 'actualLength':6}}
            -->
          </span>
        </fieldset> 
        <fieldset>
          <label>手机号:
            <input type="text" formControlName="phone"/>
          </label>
          <span *ngIf="phone.invalid && (phone.touched || phone.dirty)">
            请输入手机号
          </span>
        </fieldset>
        <fieldset>
          <label>性别:
            <input type="radio" formControlName="sex" value="1"/><input type="radio" formControlName="sex" value="0"/></label>
        </fieldset>
        <fieldset>
          <form formGroupName="pwd">
            <label>密码:
              <input type="text" formControlName="password"/>
            </label>
            <label>确认密码:
              <input type="text" formControlName="confirmPassword"/>
            </label>
          </form>
        </fieldset>
        <button type="submit">提交</button>
        <button (click)="updateFormData()">更新表单</button>
      </form>
      

    7.自定义表单规则

    • 查看源码可以发现定义表单规则函数参数是AbstractControl类型对象,返回是ValidationErrors类型对象,即具体的错误提示对象

    • 使用时可以直接使用对应的表单校验规则函数

    • 如果是FormGroup嵌套FormGroup的类型,则可以在get方法中参数使用子FormGroup.字段类型的方式获取对应的属性值

    • 错误内容显示在页面即可以使用errors类型对象的自定义错误键获取错误提示内容

    • 校验器定义代码

      import { AbstractControl, ValidationErrors } from "@angular/forms";
      
      export function passwordValidator (control: AbstractControl) : ValidationErrors | null {
          const reg = /^[A-Z][\w\_\-]{2,5}$/;
          return reg.test(control.value) ? null : {passwordError: '密码必须以大写字母开头,只能包含数字、字母、下划线、中划线、长度在3-6之间'};
      }
      
    • TypeScript代码

      import { Component, OnInit } from '@angular/core';
      import { FormGroup, FormControl, Validators, AbstractControl } from '@angular/forms';
      import { passwordValidator } from './Validators';
      
      @Component({
        selector: 'app-FormGroupTest',
        templateUrl: './FormGroupTest.component.html',
        styleUrls: ['./FormGroupTest.component.css']
      })
      export class FormGroupTestComponent {
        userInfo = new FormGroup({
          userName: new FormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(5)]),
          // 可以设置第二个参数为一个AbstractControlOptions类型
          // 在该对象类型中可以传递validators / asyncValidators / updateOn
          // 其中updateOn表示当什么时间触发校验
          phone: new FormControl('', {validators: [Validators.required, Validators.pattern(/^1[34578]\d{9}$/)], updateOn:'blur'}),
          sex: new FormControl('1', [Validators.required]),
          pwd: new FormGroup({
            password: new FormControl('', passwordValidator),
            confirmPassword: new FormControl('', passwordValidator)
          })
        });
        onSubmit() {
          console.log(this.userInfo.value);
        }
        updateFormData() {
          // this.userInfo.patchValue({'userName':'Jack'});
          this.userInfo.get('userName').setValue('Jerry');
        }
        get userName(): AbstractControl {
          return this.userInfo.get('userName');
        }
        get phone(): AbstractControl {
          return this.userInfo.get('phone');
        }
        get password(): AbstractControl {
          return this.userInfo.get('pwd.password');//通过formGroup.字段名的方式获取具体表单字段
        }
        get confirmPassword(): AbstractControl {
          return this.userInfo.get('pwd.confirmPassword');
        }
      }
      
    • HTML代码

      <form [formGroup]="userInfo" (ngSubmit)="onSubmit()">
        <fieldset>
          <label>账号:
            <input type="text" formControlName="userName"/>
          </label>
          <!-- 判断如果用户名的表单无效且是在被点击或dirty状态时显示错误内容 -->
          <span *ngIf="userName.invalid && (userName.touched || userName.dirty)">
            <!-- 使用errors对象可以获取到对应的错误内容 -->
            <span *ngIf="userName.errors.required">请输入用户名</span>
            <span *ngIf="userName.errors.minlength">用户名的长度不能小于{{userName.errors.minlength.requiredLength}}个字符</span>
            <span *ngIf="userName.errors.maxlength">用户名的长度不能超过{{userName.errors.maxlength.requiredLength}}个字符</span>
            <!--
              检验都通过返回null
              当required条件没通过时返回的是: {'required': true} 
              当length条件没通过时返回的是: {'maxlength': {'requiredLength':5, 'actualLength':6}}
            -->
          </span>
        </fieldset> 
        <fieldset>
          <label>手机号:
            <input type="text" formControlName="phone"/>
          </label>
          <span *ngIf="phone.invalid && (phone.touched || phone.dirty)">
            请输入手机号
          </span>
        </fieldset>
        <fieldset>
          <label>性别:
            <input type="radio" formControlName="sex" value="1"/><input type="radio" formControlName="sex" value="0"/></label>
        </fieldset>
        <fieldset>
          <form formGroupName="pwd">
            <label>密码:
              <input type="text" formControlName="password"/>
            </label>
            <!-- 直接通过返回的errors中自己定义的错误键获取即可 -->
            <span *ngIf="password.invalid && (password.touched || password.dirty)">
              {{password.errors.passwordError}}
            </span>
            <label>确认密码:
              <input type="text" formControlName="confirmPassword"/>
            </label>
            <span *ngIf="confirmPassword.invalid && (confirmPassword.touched || confirmPassword.dirty)">
              {{password.errors.passwordError}}
            </span>
          </form>
        </fieldset>
        <button type="submit">提交</button>
        <button (click)="updateFormData()">更新表单</button>
      </form>
      

    8.给FormGroup添加校验规则

    • 完成功能:校验密码与确认密码值是否相同,且定义在一个FormGroup中

    • 定义参数是FormGroup的校验规则作为新的校验器

      // 用于校验表单中密码重复输入是否相同
      export function passwordEqualValidator(group: FormGroup): ValidationErrors | null {
          const password = group.get('password').value;
          const confirm = group.get('confirmPassword').value;
          return password === confirm ? null : {notEqual: '两次输入的密码不一致.'}
      }
      
    • 将passwordEqualValidator作为FormGroup第二个参数进行赋值,并定义对应FormGroup的get方法,方便在html中进行获取

      import { Component, OnInit } from '@angular/core';
      import { FormGroup, FormControl, Validators, AbstractControl } from '@angular/forms';
      import { passwordValidator, passwordEqualValidator } from './Validators';
      
      @Component({
        selector: 'app-FormGroupTest',
        templateUrl: './FormGroupTest.component.html',
        styleUrls: ['./FormGroupTest.component.css']
      })
      export class FormGroupTestComponent {
        userInfo = new FormGroup({
          userName: new FormControl('', [Validators.required, Validators.minLength(3), Validators.maxLength(5)]),
          // 可以设置第二个参数为一个AbstractControlOptions类型
          // 在该对象类型中可以传递validators / asyncValidators / updateOn
          // 其中updateOn表示当什么时间触发校验
          phone: new FormControl('', {validators: [Validators.required, Validators.pattern(/^1[34578]\d{9}$/)], updateOn:'blur'}),
          sex: new FormControl('1', [Validators.required]),
          pwd: new FormGroup({
            password: new FormControl('', passwordValidator),
            confirmPassword: new FormControl('', passwordValidator)
          }, passwordEqualValidator)
        });
        onSubmit() {
          console.log(this.userInfo.value);
        }
        updateFormData() {
          // this.userInfo.patchValue({'userName':'Jack'});
          this.userInfo.get('userName').setValue('Jerry');
        }
        get userName(): AbstractControl {
          return this.userInfo.get('userName');
        }
        get phone(): AbstractControl {
          return this.userInfo.get('phone');
        }
        get password(): AbstractControl {
          return this.userInfo.get('pwd.password');//通过formGroup.字段名的方式获取具体表单字段
        }
        get confirmPassword(): AbstractControl {
          return this.userInfo.get('pwd.confirmPassword');
        }
        get pwd() {
          return this.userInfo.get('pwd');
        }
      }
      
    • 页面中应添加针对FormGroup的规则invalid判断,并通过errors对象获取对应的错误信息

      <form [formGroup]="userInfo" (ngSubmit)="onSubmit()">
        <fieldset>
          <label>账号:
            <input type="text" formControlName="userName"/>
          </label>
          <!-- 判断如果用户名的表单无效且是在被点击或dirty状态时显示错误内容 -->
          <span *ngIf="userName.invalid && (userName.touched || userName.dirty)">
            <!-- 使用errors对象可以获取到对应的错误内容 -->
            <span *ngIf="userName.errors.required">请输入用户名</span>
            <span *ngIf="userName.errors.minlength">用户名的长度不能小于{{userName.errors.minlength.requiredLength}}个字符</span>
            <span *ngIf="userName.errors.maxlength">用户名的长度不能超过{{userName.errors.maxlength.requiredLength}}个字符</span>
            <!--
              检验都通过返回null
              当required条件没通过时返回的是: {'required': true} 
              当length条件没通过时返回的是: {'maxlength': {'requiredLength':5, 'actualLength':6}}
            -->
          </span>
        </fieldset> 
        <fieldset>
          <label>手机号:
            <input type="text" formControlName="phone"/>
          </label>
          <span *ngIf="phone.invalid && (phone.touched || phone.dirty)">
            请输入手机号
          </span>
        </fieldset>
        <fieldset>
          <label>性别:
            <input type="radio" formControlName="sex" value="1"/><input type="radio" formControlName="sex" value="0"/></label>
        </fieldset>
        <fieldset>
          <form formGroupName="pwd">
            <label>密码:
              <input type="text" formControlName="password"/>
            </label>
            <!-- 直接通过返回的errors中自己定义的错误键获取即可 -->
            <span *ngIf="password.invalid && (password.touched || password.dirty)">
              {{password.errors.passwordError}}
            </span>
            <label>确认密码:
              <input type="text" formControlName="confirmPassword"/>
            </label>
            <span *ngIf="confirmPassword.invalid && (confirmPassword.touched || confirmPassword.dirty)">
              {{confirmPassword.errors.passwordError}}
            </span>
            <span *ngIf="pwd.invalid && !password.invalid && !confirmPassword.invalid">
              {{pwd.errors.notEqual}}
            </span>
          </form>
        </fieldset>
        <button type="submit">提交</button>
        <button (click)="updateFormData()">更新表单</button>
      </form>
      

    9.动态获得表单中FormControl的值

    • 动态获得FormControl的值

      this.formGroupName.get('FormControlName').valueChanges.subscribe(value=>{
          ...
      })
      
    • 通过FormControl的setValidators方法设置Validators

      this.formGroupName.get('FormControlName').setValidators([...]);
      
    • 通过FormControl的clearValidators方法设置情况所有校验器

      this.formGroupName.get('FormControlName').clearValidators();
      
    • 通过调用FormControl的updateValueAndValidity方法更新数据值和校验器

      this.formGroupName.get('FormControlName').updateValueAndValidity();
      

    10.使用FormBuilder服务简化表单创建过程

    • 在实际开发中,如果使用new FormGroup() new FormControl()会使代码比较笨重,可以使用Angular内置的服务FromBuilder来简化表单创建

    • FormBuilder的使用

      • 在constructor中注入FormBuilderconstructor(private fb: FormBuilder)

      • 可以通过this.fb.group({}创建FormGroup,this.fb.control()创建FormControl,this.fb.array()创建FormArray

      • 通过FormBuilder可以将代码简化成如下

        userInfo = this.fb.group({
            userName: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(5)]],
            phone: ['', {validators: [Validators.required, Validators.pattern(/^1[34578]\d{9}$/)], updateOn:'blur'}],
            ...
        });
        
    展开全文
  • angular 响应式表单

    2021-09-24 14:44:37
    Angular 提供了两种不同的方法来通过表单处理用户输入:响应式表单和模板驱动表单。 响应式表单:提供对底层表单对象模型直接、显式的访问。它们与模板驱动表单相比,更加健壮。如果表单是你的应用程序的关键部分,...

    响应式表单

    Angular 提供了两种不同的方法来通过表单处理用户输入:响应式表单模板驱动表单

    • 响应式表单:提供对底层表单对象模型直接、显式的访问。它们与模板驱动表单相比,更加健壮。如果表单是你的应用程序的关键部分,或者你已经在使用响应式表单来构建应用,那就使用响应式表单。
    • 模板驱动表单:依赖模板中的指令来创建和操作底层的对象模型。它们对于向应用添加一个简单的表单非常有用,比如电子邮件列表注册表单。

    这里只介绍响应式表单,模板驱动表单请参考官网—https://angular.cn/guide/forms-overview#setup-in-template-driven-forms

    全局注册响应式表单模块 ReactiveFormsModule

    要使用响应式表单控件,就要从 @angular/forms 包中导入 ReactiveFormsModule,并把它添加到你的 NgModuleimports数组中。如下:app.module.ts

    /***** app.module.ts *****/
    import { ReactiveFormsModule } from '@angular/forms';
    
    @NgModule({
      imports: [
        // other imports ...
        ReactiveFormsModule
      ],
    })
    export class AppModule { }
    

    添加基础表单控件 FormControl

    使用表单控件有三个步骤。

    1. 在你的应用中注册响应式表单模块。该模块声明了一些你要用在响应式表单中的指令。
    2. 生成一个新的 FormControl 实例,并把它保存在组件中。
    3. 在模板中注册这个 FormControl

    要注册一个表单控件,就要导入FormControl类并创建一个 FormControl的新实例,将其保存为类的属性。如下:test.component.ts

    /***** test.component.ts *****/
    import { Component } from '@angular/core';
    import { FormControl } from '@angular/forms';
    
    @Component({
      selector: 'app-name-editor',
      templateUrl: './name-editor.component.html',
      styleUrls: ['./name-editor.component.css']
    })
    export class TestComponent {
    	// 可以在 FormControl 的构造函数设置初始值,这个例子中它是空字符串
      name = new FormControl('');
    }
    

    然后在模板中注册该控件,如下:test.component.html

    <!-- test.component.html -->
    <label>
      Name: <input type="text" [formControl]="name">
    </label>
    <!-- input 中输入的值变化的话,这里显示的值也会跟着变化 -->
    <p>name: {{ name.value }}</p>
    

    FormControl 的其它属性和方法,参阅 API 参考手册

    把表单控件分组 FormGroup

    就像FormControl 的实例能让你控制单个输入框所对应的控件一样,FormGroup 的实例也能跟踪一组 FormControl 实例(比如一个表单)的表单状态。当创建 FormGroup 时,其中的每个控件都会根据其名字进行跟踪。

    看下例演示:test.component.tstest.component.html

    import { Component } from '@angular/core';
    import { FormControl, FormGroup, Validators } from '@angular/forms'
    
    @Component({
      selector: 'app-test',
      templateUrl: './test.component.html',
      styleUrls: ['./test.component.css']
    })
    export class TestComponent implements OnInit {
        constructor() {}
    
        profileForm = new FormGroup({
          firstName: new FormControl('', [Validators.required,Validators.pattern('[a-zA-Z0-9]*')]),
          lastName: new FormControl('', Validators.required),
        });
    	
    	onSubmit() {
    		// 查看控件组各字段的值
          console.log(this.profileForm.value)
        }
    }
    
    <!-- profileForm 这个 FormGroup 通过 FormGroup 指令绑定到了 form 元素,在该模型和表单中的输入框之间创建了一个通讯层 -->
    <!-- FormGroup 指令还会监听 form 元素发出的 submit 事件,并发出一个 ngSubmit 事件,让你可以绑定一个回调函数。 -->
    <form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
        <label>
    <!-- 由 FormControlName 指令把每个输入框和 FormGroup 中定义的表单控件 FormControl 绑定起来。这些表单控件会和相应的元素通讯 -->
          First Name: <input type="text" formControlName="firstName">
        </label>
        <label>
          Last Name: <input type="text" formControlName="lastName">
        </label>
        <button type="submit" [disabled]="!profileForm.valid">Submit</button>
      </form>
    
      <p>{{ profileForm.value }}</p>
      <!-- 控件组的状态: INVALID 或 VALID -->
      <p>{{ profileForm.status }}</p>	
      <!-- 控件组输入的值是否为有效值: true 或 false-->
      <p>{{ profileForm.valid }}</p>
      <!-- 是否禁用: true 或 false-->
      <p>{{ profileForm.disabled }}</p>
    

    FormGroup 的其它属性和方法,参阅 API 参考手册

    使用更简单的 FormBuilder 服务生成控件实例

    在响应式表单中,当需要与多个表单打交道时,手动创建多个表单控件实例会非常繁琐。FormBuilder服务提供了一些便捷方法来生成表单控件。FormBuilder在幕后也使用同样的方式来创建和返回这些实例,只是用起来更简单。

    FormBuilder 是一个可注入的服务提供者,它是由 ReactiveFormModule 提供的。只要把它添加到组件的构造函数中就可以注入这个依赖。

    FormBuilder服务有三个方法:control()group()array()。这些方法都是工厂方法,用于在组件类中分别生成FormControlFormGroupFormArray

    看下例演示:test.component.ts

    import { Component } from '@angular/core';
    // 1、导入 FormBuilder
    import { FormBuilder, Validators } from '@angular/forms';
    
    @Component({
      selector: 'app-test',
      templateUrl: './test.component.html',
      styleUrls: ['./test.component.css']
    })
    export class TestComponent {
    	// 2、注入 FormBuilder 服务
        constructor(private fb: FormBuilder) { }
        ngOnInit() { }
    
        profileForm = this.fb.group({
          firstName: ['', [Validators.required, Validators.pattern('[a-zA-Z0-9]*')]],
          lastName: ['', Validators.required],
        });
        // 相当于
        // profileForm = new FormGroup({
        //   firstName: new FormControl('', [Validators.required,Validators.pattern('[a-zA-Z0-9]*')]),
        //   lastName: new FormControl('', Validators.required),
        // });
    
        onSubmit() {
          console.log(this.profileForm.value)
          console.log(this.profileForm)
        }
    }
    

    对比可以发现,使用FormBuilder服务可以更方便地生成FormControlFormGroupFormArray,而不必每次都手动new一个新的实例出来。

    表单验证器 Validators

    Validators类验证器的完整API列表,参考API手册

    验证器(Validators)函数可以是同步函数,也可以是异步函数。

    • 同步验证器:这些同步函数接受一个控件实例,然后返回一组验证错误或 null。你可以在实例化一个 FormControl 时把它作为构造函数的第二个参数传进去。
    • 异步验证器 :这些异步函数接受一个控件实例并返回一个 PromiseObservable,它稍后会发出一组验证错误或 null。在实例化 FormControl 时,可以把它们作为第三个参数传入。

    出于性能方面的考虑,只有在所有同步验证器都通过之后,Angular 才会运行异步验证器。当每一个异步验证器都执行完之后,才会设置这些验证错误。

    验证器Validators类的API

    https://angular.cn/api/forms/Validators

    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
    }
    

    内置验证器函数

    要使用内置验证器,可以在实例化FormControl控件的时候添加

    import { Validators } from '@angular/forms';
    ...
    ngOnInit(): void {
      this.heroForm = new FormGroup({
      // 实例化 FormControl 控件
        name: new FormControl(this.hero.name, [
          Validators.required,	// 验证,必填
          Validators.minLength(4),	// 长度不小于4
          forbiddenNameValidator(/bob/i) // 自定义验证器
        ]),
        alterEgo: new FormControl(this.hero.alterEgo),
        power: new FormControl(this.hero.power, Validators.required)
      });
    }
    get name() { return this.heroForm.get('name'); }
    
    get power() { return this.heroForm.get('power'); }
    

    自定义验证器

    自定义验证器的内容请参考API手册

    有时候内置的验证器并不能很好的满足需求,比如,我们需要对一个表单进行验证,要求输入的值只能为某一个数组中的值,而这个数组中的值是随程序运行实时改变的,这个时候内置的验证器就无法满足这个需求,需要创建自定义验证器。

    • 在响应式表单中添加自定义验证器。在上面内置验证器一节中有一个forbiddenNameValidator函数如下:

      import { Validators } from '@angular/forms';
      ...
      ngOnInit(): void {
        this.heroForm = new FormGroup({
          name: new FormControl(this.hero.name, [
            Validators.required,
            Validators.minLength(4),
            // 1、添加自定义验证器
            forbiddenNameValidator(/bob/i)
          ])
        });
      }
      // 2、实现自定义验证器,功能为禁止输入带有 bob 字符串的值
      export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
        return (control: AbstractControl): ValidationErrors | null => {
          const forbidden = nameRe.test(control.value);
          // 3、在值有效时返回 null,或无效时返回验证错误对象
          return forbidden ? {forbiddenName: {value: control.value}} : null;
        };
      }
      

      验证器在值有效时返回 null,或无效时返回验证错误对象。 验证错误对象通常有一个名为验证秘钥(forbiddenName)的属性。其值为一个任意词典,你可以用来插入错误信息({name})。

    • 在模板驱动表单中添加自定义验证器。要为模板添加一个指令,该指令包含了 validator 函数。同时,该指令需要把自己注册成为NG_VALIDATORS的提供者。如下所示:

      // 1、导入相关类
      import { NG_VALIDATORS, Validator, AbstractControl, ValidationErrors } from '@angular/forms';
      import { Input } from '@angular/core'
      
      @Directive({
        selector: '[appForbiddenName]',
        // 2、注册成为 NG_VALIDATORS 令牌的提供者
        providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
      })
      export class ForbiddenValidatorDirective implements Validator {
        @Input('appForbiddenName') forbiddenName = '';
        // 3、实现 validator 接口,即实现 validate 函数
        validate(control: AbstractControl): ValidationErrors | null {
        	// 在值有效时返回 null,或无效时返回验证错误对象
          return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
                                    : null;
        }
      }
      // 4、自定义验证函数
      export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
        return (control: AbstractControl): ValidationErrors | null => {
          const forbidden = nameRe.test(control.value);
          // 3、在值有效时返回 null,或无效时返回验证错误对象
          return forbidden ? {forbiddenName: {value: control.value}} : null;
        };
      }
      

      注意,自定义验证指令是用 useExisting 而不是 useClass 来实例化的。如果用useClass来代替 useExisting,就会注册一个新的类实例,而它是没有forbiddenName 的。

      <input type="text" required appForbiddenName="bob" [(ngModel)]="hero.name">
      
    展开全文
  • angular响应式表单

    2018-11-24 19:52:38
    angular中表单有两种形式,一种是模板驱动表单,一种是响应式表单,模板驱动表单跟angularjs的表单差不多,都是在模板中进行数据绑定,验证等,而响应式表单是功能更强大,灵活的表单形式。 FormControl表示表单控...

    在使用ng-zorro的表单时,发现他和angular的表单有很大不同,于是就去学习了一下angular的表单。
    在angular中表单有两种形式,一种是模板驱动表单,一种是响应式表单,模板驱动表单跟angularjs的表单差不多,都是在模板中进行数据绑定,验证等,而响应式表单是功能更强大,灵活的表单形式。

    FormControl表示表单控件

    响应式表单是用模型驱动来处理表单与用户交互的,在响应式表单中每个表单控件都是一个模型。使用响应式表单时,需要在导入@angular/forms 包中导入 ReactiveFormsModule并在模块中加入imports数组中.

    clipboard.png

    创建一个表单控件

    在组件类中导入FormControl类,创建一个FromControl类实例,他将关联到表单中的一个控件.

    clipboard.png'

    之后在模板中用fromControl指令把模板中表单控件关联到name对象:``

    <label>
      Name:
      <input class="form-control" type="text" [formControl]="name">
    </label>
    <p>
      {{name.value}}
    </p>
    

    clipboard.png

    这个时候我们的name对象就关联到了模板中的input表单控件了,用name.value属性就可以看到对象的值,他是与视图值绑定在一起的.此时这个input控件就由name这个模型管理,获取值,修改值,验证都通过这个模型进行.

    FolmGroup管理FormContrl

    将控件合并

    在表单中,通常有多个控件,把多个控件合并在一起有助于管理,可以用FormGroup来管理控件FormContrl.
    从@angular/forms包中导入FormGroup,新建一个FormGroup对象,在构造函数中传入一个对象,属性名代表控件的名字,值就是一个FormContrl实例.

    clipboard.png

    关联FormGroup模型到模板视图

    在模板的表单中用FormGroup指令来关联模型,由 FormControlName指令提供的formControlName属性把每个输入框和 FormGroup 中定义的表单控件绑定起来。这样在视图表单控件值修改时,会反应到FormGroup上.

    <form [formGroup]="teacher">
    <label>
      TeacherName:
      <input class="form-control" type="text" formControlName="name">
    </label>
      <label>
        TeacherEmail:
        <input class="form-control" type="text" formControlName="email">
      </label>
    </form>
    <pre>{{teacher.value|json}}</pre>

    clipboard.png

    用FormBuilder简化生成控件

    因为表单使用很频繁,手动创建多个表单控件会非常繁琐,可以使用FoRmBuilder服务来简化创建过程:
    导入@angular/forms 包中导入 FormBuilder类,在构造函数中注入服务,使用服务方法来简化生成过程:

    constructor(private fb: FormBuilder) {
      }

    生成对比:

    name = new FormControl('');
      builderName = this.fb.control('');
    
      teacher = new FormGroup({
        name: new FormControl(''),
        email: new FormControl('')
      });
    
      builderTeacher = this.fb.group({
        name: [''],
        email: ['']
      });

    动态表单

    在之前使用angularjs开发时,很多表单都很相似,但是却不得不写多个相似的表单,使用响应式表单可以将这些表单都抽象出来,动态生成表单,使得不用写重复的代码,而且更易于维护。
    先比较这两个控件,一个input一个select下拉框:

    clipboard.png

    这两个控件有很多的共同点,他们都是html标签,都有一个label,一个id,一个formcontrolName,一个type,且控件都有值(.value),只是他们的值都是不同的,可以把这两个控件抽象成一个基类对象,他有id,lable,html标签类型,value属性,在通过继承基类对象生成对应的控件.

    构建对象模型

    基类对象

      value: T;                 //控件的值    
      key: string;              //控件名字
      label: string;            //控件描述
      controlType: string;      //html标签类型
    constructor(options: {
        value?: T,
        key?: string,
        label?: string,
        controlType?: string
      } = {}) {
        this.value = options.value;
        this.key = options.key || '';
        this.label = options.label || '';
        this.controlType = options.controlType || '';
      }
    

    input对象

    import {BaseOb} from './base-ob';
    
    export class InputText extends BaseOb{
      controlType = 'input';
      type: string;
    
      constructor(options: {} = {}) {
        super(options);
        this.type = options['type'] || '';
      }
    }

    通过继承BaseOb对象,并声明自己为input(html类型),加上一个type(text)

    select对象:

    import {BaseOb} from './base-ob';
    
    export class Select extends BaseOb{
      controlType = 'select';
      options: {key: string, value: string}[] = [];
    
      constructor(options: {} = {}) {
        super(options);
        this.options = options['options'] || [];
      }
    }

    select不需要type类型,但他需要一个options来循环生成下拉框

    将对象模型数组转化为同一个FormGroup

    我们需要把这个input和select转化为一个FormGroup来与模板视图交互,因此需要一个服务把BaseOb数组转化为一个FormGroup对象:

    import { Injectable } from '@angular/core';
    import {BaseOb} from './base-ob';
    import {FormControl, FormGroup} from '@angular/forms';
    
    @Injectable({
      providedIn: 'root'
    })
    export class BaseObService {
    
      constructor() { }
    
      toFormGroup(obs: BaseOb<any>[] ) {
        const group: any = {};
    
        obs.forEach(ob => {
          group[ob.key] = new FormControl(ob.value || '');    // 以key作为FormControl的名字,value作为值
        });
        
        return new FormGroup(group);                         // 将对象传入formGroup构造函数生成FormGroup
      }
    }

    将模型对象转化为模板视图

    有了模型对象,就得把模型对象转化为之前的视图,用一个组件来做这件事:

    import {Component, Input, OnInit} from '@angular/core';
    import {BaseOb} from '../base-ob';
    import {FormGroup} from '@angular/forms';
    
    @Component({
      selector: 'app-element',
      templateUrl: './element.component.html',
      styleUrls: ['./element.component.css']
    })
    export class ElementComponent implements OnInit {
      @Input() element: BaseOb<any>;
      @Input() form: FormGroup;
      constructor() { }
    
      ngOnInit() {
      }
    
    }

    这个组件类首先接受一个抽象的基类对象和一个FormGoup,用Input()获取,然后再在模板中根据element生成相应的控件:

    <div [formGroup]="form">
      <label [attr.for]="element.key">{{element.label}}</label>
    
      <div [ngSwitch]="element.controlType">
    
        <input *ngSwitchCase="'input'" [formControlName]="element.key"
               [id]="element.key" [type]="element.type">
    
        <select [id]="element.key" *ngSwitchCase="'select'" [formControlName]="element.key">
          <option *ngFor="let opt of element.options" [value]="opt.key">{{opt.value}}</option>
        </select>
      </div>
    </div>

    用ngSwitch来判断,如果当前的控件html属性为input显示<input>,为select显示<select>,其余如此.

    使用动态表单

    通过生成BaseOb数组并用ngfor循环生成<app-element>就可以动态的生成表单控件了:

    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements OnInit{
      obs: BaseOb<any>[];
      form: FormGroup;
    
      constructor(private obService: BaseObService) {  }
    
      ngOnInit(): void {
        this.obs = [
          new InputText({
            key: 'name',
            label: 'teacher name',
            value: 'zhansan',
            type: 'text'
          }),
          new Select({
            key: 'klass',
            label: 'klasses',
            options: [
              {key: '1',  value: '一班'},
              {key: '2',  value: '二班'},
              {key: '3',   value: '三班'},
              {key: '4', value: '四班'}
            ]
          })
        ];
        this.form = this.obService.toFormGroup(this.obs);
      }
    }

    在组件中新建一个input(text)模型和一个select模型,通过服务获取表单组,之后在组件模板中调用<app-element>

    <h2>动态表单生成:</h2>
    <div style="width: 200px;">
      <form [formGroup]="form">
    
        <div *ngFor="let o of obs">
          <app-element [element]="o" [form]="form"></app-element>
        </div>
      </form>
    </div>
    <pre>{{form.value|json}}</pre>

    循环obs数组,app-element组件会根据遍历对象生成相应的控件并绑定.
    效果:

    clipboard.png]
    可以用服务来生成BaseOb对象数组来定义我们需要的表单控件,验证信息也可以通过这样类似的方法生成,只需要提供表单控件各自的属性,统一生成表单控件,便于维护和编写。

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,329
精华内容 1,731
关键字:

angular响应式表单