首頁  >  文章  >  web前端  >  一文淺析Angular中的響應式表單

一文淺析Angular中的響應式表單

青灯夜游
青灯夜游轉載
2022-04-25 10:26:403324瀏覽

本篇文章帶大家聊聊Angular中的響應式表單,透過實例來介紹簡單的表單實作方法,希望對大家有幫助!

一文淺析Angular中的響應式表單

由於最近公司框架升級,拋棄了原來手動檢驗表單的方式,將所有的表單改為響應式,由於之前沒用過,在一開始我以為只有我沒有用過,了解了小組裡的其他同事得知基本上都不是很熟悉

後面比較緊,沒辦法只能邊做邊學邊改了,所以難免踩了一些坑,當然也花了一些時間學習,雖然對於熟悉的人來說可能很簡單,但是還是將學習的過程和小結以及解決的問題的方法總結一下,也算是一種提煉。這裡更多的是理論結合實際業務需求來說,而不是一味的按照官方文件的方式寫API介紹,如果那樣就是學習筆記,而不是總結了。

為什麼主要介紹響應式表單呢?因為響應式表單提供對底層表單物件模型直接、明確的存取。它們與範本驅動表單相比,更加健壯:它們的可擴展性、可重複使用性和可測試性都更高。適用於比較複雜的表單,其實最重要的是其他的我也不會呀。 【相關教學推薦:《angular教學》】

一、響應式表單基本概念

1.FormControl  、FormArray 、FormGroup

1.FormControl:  用於追蹤單一表單控制項的值和驗證狀態,例如一個欄位綁定

//初始化一个栏位的值为测试名字,并且不可用
const Name:FormControl = new FormControl({value:'测试名字', disabled: true });

2. FormArray:用於追蹤表單控制項陣列的值和狀態,例如幾個欄位一起,常用的表格或在表單中嵌入表格

//定义表单对象的属性为aliases的FormArray 
this.validateForm = this.fb.group({aliases: this.fb.array([]),});

//获取FormArray 
get aliases() {return this.validateForm.get('aliases') as FormArray;}

//给FormArray 添加item
this.aliases.push(
  this.fb.group({Id: 0,Name: [null],})
);

3.FormGroup:用於追蹤單一表單控制項的值和驗證狀態,它可以包含單一或多個FormControl 和FormArray ,一般一個表單對應一個FormGroup實例,而表單的各個欄位對應FormControl  和FormArray ,當然他們可以互相嵌套,例如FormArray中可以嵌套FormGroup,它的靈活性就是如此。

validateForm =  new FormGroup({Name: new FormControl({value:'测试名字', disabled: true }),});
validateForm = this.fb.group({});

4.FormBuilder:是一個可注入的服務提供者,手動建立多個表單控制項實例會非常繁瑣,FormBuilder 服務提供了一些便捷方法來產生表單控制項,以前每一個建立要先生成FormGroup 然後產生FormControl,而使用FormBuilder的group方法可以減少重複程式碼,說白了就是幫助方便產生表單

validateForm!: FormGroup;
//手动创建
validateForm = new FormGroup({
    Name: new FormControl('测试名字'),
  });
  
//FormBuilder表单构建器
validateForm = this.fb.group({
  Name:[ { value:'测试名字',disabled:true}],
});

2.Validator 表單驗證

表單驗證用於確保使用者的輸入是完整且正確的。如何把單一驗證器加入到表單控制項中,以及如何顯示表單的整體狀態,通常驗證器傳回null表示所有的驗證通過。

1.同步驗證器:同步驗證器函數接受一個控制項實例,然後傳回一組驗證錯誤或null,在實例化FormControl 時可以將他作為第二個參數傳入

 //formControlName的值必须和ts代码中FormControl 的实例一致
 <input type="text" id="name" class="form-control" formControlName="name" required>
 
 //判断对应的FormControl 是否没通过校验 而有错误信息
 <div *ngIf="name.errors?.[&#39;required&#39;]">
    Name is required.
 </div>
//初始化一个栏位并且加入必填校验验证器
const name:FormControl = new FormControl({&#39;测试名字&#39;, disabled: true },Validators.required,);

//获取这个FormControl
get name() { return this.heroForm.get(&#39;name&#39;); }

2.非同步驗證器:非同步函數接受一個控制項實例並傳回一個Promise 或Observable ,只有在所有同步驗證器都通過之後,Angular才會執行非同步驗證器,在實例化FormControl 時可以將他作為第三個參數傳入

#3.內建驗證器:例如驗證一些長度,不能為空可以使用已經提供的Validator 類別來實作

4.自訂驗證器:系統內部提供的驗證器無法滿足現有需求,可以使用自訂驗證器做一些個人化的校驗,自訂校驗器必須傳回ValidationErrors類型或空

 //formControlName的值必须和ts代码中FormControl 的实例一致
 <input type="text" id="name" class="form-control" formControlName="name" required>
 
 //判断对应的FormControl 是否没通过校验 而有错误信息
 <div *ngIf="name.hasError(&#39;Invalid&#39;)">
    名字也太长了吧....
 </div>
//初始化一个栏位并且加入必填校验验证器
const name:FormControl = new FormControl({&#39;测试名字&#39;, disabled: true },this.CustomValidators());

CustomValidators() {
 return (control: AbstractControl): ValidationErrors | null => {
    if(control.value.length!=10)
      {
        return {Invalid:true}
      }
      return null;
    };
}

#3.表單及元素的基本方法與屬性

  • 方法
重新計算表單FormControl 控制項的值和驗證狀態等給表單FormControl 控制項設定驗證器,若設定多個就用數組"給表單FormControl 控制項設定不可用,注意當FormControl 是disabled時,表單的常規取值getValue()對應值會為空,可用getRawValue()取原始值物件得到對應FormControl 的值啟用表單FormControl 控制項
#方法 使用效果
setValue() 使用setVlue可以設定控制項FormControl  的值,但是使用時必須FormGroup所有的屬性一起賦值,不能單一賦值,常用在修改載入賦值。
patchValue() 使用patchValue也可以設定FormControl的值,可以依照需要設定指定的FormControl,而不需要全部設定,常用在更新某個欄位值
reset () FormControl 中使用重置目前控制項所有狀態,在FormGroup中使用就是重置表單物件裡的內容,例如控制項被設為不可用disabled,control.reset({ value: 'Drew', disabled: true });
markAsPristine() 是將表單控制項值標記為未改變,這個方法主要用在表單重置時,此時它的狀態pristine為true
markAsDirty() 是將表單FormControl 控制項值標記為已改變,此時它的狀態Dirty為true
##updateValueAndValidity( )
setValidators() setValidators([v1,v2,v3])"
#disable()
enable()

##屬性#屬性使用方法說明toucheduntouchedpristine
#當表單FormControl 控制項的touched為true表示控制項已經被取得焦點,反之同理
當untouched 為true表示控制項未被取得焦點,反之同理
###表示表單元素是純淨的,使用者未操作過,可以使用markAsPristine方法設為true################ ########表示表單元素是已被使用者操作過,可以使用markAsDirty方法設為true###############status#########取得表單FormControl 控制項上的狀態###############Errors######## 取得目前控制項的錯誤資訊########### #

二.实例分析及应用

1. 简单的表单实现

######需求1

我们主要用到的框架版本是Angular 12 + NG-ZORRO, 所以在下面很多实现和示例代码将与他们有关,虽然可能代码不一样,但也只是在UI层面的区别稍微大一点点,但对于TS代码,只是换汤不换药,稍微注意一下就好了,其实下面实例中的需求,基本就是我在工作时需要做的的一些基本内容和遇到的问题,经过查阅资料后解决的思路和过程,甚至截图都一模一样。

实现最基本的表单新增功能并且校验员工ID为必填以及长度不能超过50,要实现的效果图如下

一文淺析Angular中的響應式表單

分析

1.首先需求未提出有特殊注意点,基本都是简单的输入框赋值然后保存,只要基本的概念搞清楚实现这种最简单

2.我们用一个FormGroup和6个FormControl 完成和界面绑定即可

3.绑定验证器用于校验长度和必填

实现步骤

1.定义html 表单结构

<!-- formGroup 属性绑定表单对象 -->
<form nz-form [formGroup]="validateForm" nzLayout="vertical">
  <nz-form-label nzRequired>Employee ID
  </nz-form-label>
  
   <!-- Employee_ErrorTrip为验证不通过弹出的提示信息 -->
   <!-- formControlName绑定表单元素FormControl -->
  <nz-form-control [nzErrorTip]="Employee_ErrorTrip">
    <input nz-input formControlName="EmployeeID"  placeholder="" />
  </nz-form-control>

  <ng-template #Employee_ErrorTrip let-control>
    <ng-container *ngIf="control.hasError(&#39;required&#39;)">
      员工编号为必填项目
    </ng-container>
  </ng-template>
</form>

2.在TypeScript代码中声明表单对象,在构造函数中注入FormBuilder,并且在ngOnInit中进行表单初始化

//定义表单对象
validateForm:FormGroup;

//构造函数注入FormBuilder
constructor(private fb: FormBuilder){}

//在声明周期钩子函数中初始化表单
ngOnInit() {
  //初始化并且绑定必填验证器和长度验证器

    this.validateForm = this.fb.group({
      EmployeeID: [&#39;&#39;, [Validators.required, Validators.maxLength(50)]],  
    })
}

2.在表格中应用表单

需求2

需要实现表格的表单新增和提交以及个性化定制需求,要实现的效果图和需求描述如下

1.点击Add 添加一行表格 ,编辑完毕,点击Save保存数据,点击Revoke取消编辑

2.默认开始时间和结束时间禁止使用

3.当选择Contract Type为 “短期合同” Contract start date 和Contract end date可用,当选择Contract Type为 “长期合同”不可用

4.如果Contract start date 和Contract end date可用,需要验证开始结束时间合法性,例如开始事件不能超过结束时间

一文淺析Angular中的響應式表單

分析

1.在表格中使用表单,虽然表单在表格中,但是他的每一列同样都是一个个FormControl

2.一共4列需要输入值,就说明有4个FormControl 然后最后一列就是2个按钮

3.我们根据上面的基础知识知道,FormControl 不能单独使用,所以需要被FormGroup包裹,此时说明一行对应一个FormGroup

4.由一行对应一个FormGroup知道,我们的表格时多行的,也就是有多个FormGroup,我们可以使用FormArray来存储,因为他代表一组表单组

5.根据需求第2点默认开始时间和结束时间禁止使用,我们知道在一开始初始化时,设置开始结束时间对应的FormControl 为disabled就行了

6.第3点需求需要涉及联动,也就是当Contract Type对应的FormControl 的值为“短期合同”时,需要将 “开始结束时间”对应的FormControl设置为可用,这个需要自定义验证器来完成

实现步骤

1.首先定义Html表单结构

<nz-table [nzData]="CONTRACTS" nzTableLayout="fixed" [nzShowQuickJumper]="true">
  <thead>
    <tr>
      <th>Contract type</th>
      <th>Contract start date</th>
      <th>Contract end date</th>
      <th>Agreement item</th>
      <th>Operation</th>
    </tr>
  </thead>

  <tbody>
    <!-- 绑定表单组属性aliases -->
    <ng-container formArrayName="aliases">
      <!-- 将表单组中当前行的索引与formGroup绑定 -->
      <tr [formGroupName]="i" *ngFor="let data of aliases.controls;index as i">
        <td>
          <nz-form-item>
            <nz-form-control nzSpan="1-24">
              <!-- AccountName绑定FormControl  -->
              <nz-select nzShowSearch nzAllowClear nzPlaceHolder="" formControlName="Type">
                <nz-option *ngFor="let option of Type" [nzValue]="option.Code" [nzLabel]="option.Value">
                </nz-option>
              </nz-select>
            </nz-form-control>
          </nz-form-item>
          <nz-form-item>
            <nz-form-control nzSpan="1-24" [nzErrorTip]="StartDate">
              <nz-date-picker id="StartDate" formControlName="StartDate" nzPlaceHolder="">
              </nz-date-picker>
              <!-- 校验提示模板用于时间验证器 -->
              <ng-template #StartDate let-control>
              	<!-- 判断时间验证器是否存在beginGtendDate属性,如果有说明没有通过验证 然后展示提示信息 -->
                <ng-container *ngIf="control.hasError(&#39;beginGtendDate&#39;)">
                  开始时间不能晚于结束时间
                </ng-container>
              </ng-template>
            </nz-form-control>
          </nz-form-item>
          <nz-form-item>
            <nz-form-control nzSpan="1-24" [nzErrorTip]="EndDate">
              <nz-date-picker style="width: 100%;" formControlName="EndDate" nzPlaceHolder="">
              </nz-date-picker>
              <ng-template #EndDate let-control>
                <ng-container *ngIf="control.hasError(&#39;beginGtendDate&#39;)">
                  开始时间不能晚于结束时间
                </ng-container>
              </ng-template>
            </nz-form-control>
          </nz-form-item>
          <nz-form-item>
            <nz-form-control nzSpan="1-24">
              <nz-select nzShowSearch nzAllowClear nzPlaceHolder="" formControlName="ContractType">
                <nz-option *ngFor="let option of ContractTypes" [nzValue]="option.Code" [nzLabel]="option.Value">
                </nz-option>
              </nz-select>
            </nz-form-control>
          </nz-form-item>
        </td>
        <td>
          <button style="color: #009688;" nz-button nzType="text">
            <i nz-icon nzType="save"></i>Save
          </button>
          <button nz-button nzType="text" nzDanger>
            <i nz-icon nzType="redo"></i>Revoke
          </button>
        </td>
      </tr>
    </ng-container>
  </tbody>
</nz-table>

2.在TypeScript代码中声明表单对象validateForm,然后初始化一个FormArray类型的属性aliases的实例作为表格formArrayName的值

3.点击Add按钮时向表单对象validateForm的属性aliases添加一条数据

4.定义Contract Type 联动的自定义校验器 contractTypeValidation()方法

5.定义时间校验器 timeValidation()方法,如果时间不合法,将FormControl的错误状态设置属性beginGtendDate,然后在模板中根据这个属性来选择是否渲染日式信息

//定义表单对象
validateForm:FormGroup;
//构造函数注入FormBuilder
constructor(private fb: FormBuilder){}

//在声明周期钩子函数中初始化一个表单对象validateForm
ngOnInit() {
this.validateForm = this.fb.group({
      aliases: this.fb.array([]),
 	});
}
//声明aliases属性用作界面formArrayName绑定
get aliases(){
	return this.validateForm.get(&#39;aliases&#39;) as FormArray;
}

addNewRow() {
  const group = this.fb.group({
    //添加给Type初始化验证器
    Type: [null, [CommonValidators.required, this.contractTypeValidation()]],
    //初始化禁用StartDate和EndDate的FormControl 
    StartDate: [{ value: null, disabled: true }, []],
    EndDate: [{ value: null, disabled: true },[]],
    ContractType: [null, [CommonValidators.required, CommonValidators.maxLength(20)]],
  })
  this.aliases.push(group);
}

  //自定义Contract Type验证器
contractTypeValidation() {
    return (control: AbstractControl): ValidationErrors | null => {
      let contents: any[] = this.validateForm.value.aliases;
      if (control.touched && !control.pristine) {
        //获取表单组
        const formArray: any = this.validateForm.controls.aliases;
        //找到正在编辑的行的索引
        const index = contents.findIndex((x) => !x.isShowEdit);

        //获取开始结束时间FormControl 实例
        const StartDate: AbstractControl =
          formArray.controls[index].get(&#39;StartDate&#39;),
          EndDate: AbstractControl = formArray.controls[index].get(&#39;EndDate&#39;);

        if (control.value === "短期合同") {
          //给开始结束时间设置验证器用于验证时间合法性
          StartDate.setValidators([CommonValidators.required, this.timeValidation()]);
          EndDate.setValidators([this.timeValidation()]);

          //启动开始结束时间控件
          EndDate.enable();
          StartDate.enable();
        } else {
          //Contract Type不是短期合同就清除验证器
          StartDate.clearValidators();
          EndDate.clearValidators();
          //禁用开始结束时间
          EndDate.disable();
          StartDate.disable();
        }

      }
      return null;
    }
  }



  //自定义时间验证器
 timeValidation()
  {
    return (control: AbstractControl): ValidationErrors | null => {
      if (!control.pristine) {
        let contents: any[] = this.validateForm.value.aliases;
        const formArray: any = this.validateForm.controls.aliases;
        const index = contents.findIndex((x) => !x.isShowEdit);
        //获取开始结束时间FormControl实例
        const EndDate: string = formArray.controls[index].get(&#39;EndDate&#39;).value;
        const StartDate: string =formArray.controls[index].get(&#39;StartDate&#39;).value;

        if (EndDate === null || StartDate === null) return null;

        //如果时间不合法,那就设置当前控件的错误状态 beginGtendDate为true
        if (
          Date.parse(control.value) > Date.parse(EndDate) ||
          Date.parse(control.value) < Date.parse(StartDate)
        ) {
          return { beginGtendDate: true };
        }
      }
      return null;
    }
  }

更多编程相关知识,请访问:编程入门!!

以上是一文淺析Angular中的響應式表單的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除