MST

星途 面试题库

面试题:Angular模板驱动表单在复杂嵌套场景下的双向数据绑定与验证处理

有一个复杂的Angular模板驱动表单场景,包含多级嵌套的表单组和表单数组。例如,一个订单表单,订单包含多个商品(表单数组),每个商品又有不同的属性(表单组),并且所有表单元素都需要进行双向数据绑定和验证。阐述如何设计整个表单结构,确保双向数据绑定正常工作,以及如何在提交表单时进行全面且高效的验证,处理可能出现的各种验证错误,并给出核心代码示例。
19.2万 热度难度
前端开发Angular

知识考点

AI 面试

面试题答案

一键面试

1. 设计表单结构

  1. 使用 FormGroupFormArray
    • 顶级 FormGroup 代表整个订单表单。
    • 对于订单中的商品(表单数组),使用 FormArray
    • 每个商品的属性(表单组)使用 FormGroup
  2. 双向数据绑定
    • 在模板中,使用 [(ngModel)] 进行双向数据绑定,并结合 name 属性。例如:
    <input type="text" [(ngModel)]="data.property" name="property">
    
    • 对于 FormArray 中的元素,在 *ngFor 循环中绑定数据。例如:
    <div formArrayName="products">
      <div *ngFor="let product of orderForm.get('products').controls; let i = index" [formGroupName]="i">
        <input type="text" [(ngModel)]="product.name" name="name">
      </div>
    </div>
    
  3. 验证
    • 对于单个表单控件,可以使用 Angular 内置的验证器,如 requiredminlength 等。例如:
    <input type="text" [(ngModel)]="data.property" name="property" required>
    
    • 对于 FormGroupFormArray,可以自定义验证器。例如,自定义一个验证商品数量不能为负数的验证器:
    export function quantityValidator(control: AbstractControl): { [key: string]: any } | null {
      const quantity = control.get('quantity');
      if (quantity && quantity.value < 0) {
        return { negativeQuantity: true };
      }
      return null;
    }
    
    • 在创建 FormGroup 时应用自定义验证器:
    const productFormGroup = new FormGroup({
      name: new FormControl('', Validators.required),
      quantity: new FormControl(0, [Validators.required, Validators.min(0)]),
    }, { validators: quantityValidator });
    

2. 提交表单时的验证及错误处理

  1. 验证表单有效性
    • 在提交按钮的点击事件中,检查顶级 FormGroup 的有效性。例如:
    <button (click)="onSubmit()" [disabled]="!orderForm.valid">提交</button>
    
    onSubmit() {
      if (this.orderForm.valid) {
        // 处理表单提交逻辑
      } else {
        // 处理验证错误
        this.validateAllFormFields(this.orderForm);
      }
    }
    
  2. 处理验证错误
    • 遍历表单控件,标记所有无效控件,以便在模板中显示错误信息。
    validateAllFormFields(formGroup: FormGroup) {
      Object.keys(formGroup.controls).forEach(field => {
        const control = formGroup.get(field);
        if (control instanceof FormControl) {
          control.markAsTouched({ onlySelf: true });
        } else if (control instanceof FormGroup) {
          this.validateAllFormFields(control);
        } else if (control instanceof FormArray) {
          control.controls.forEach(c => {
            if (c instanceof FormGroup) {
              this.validateAllFormFields(c);
            }
          });
        }
      });
    }
    
    • 在模板中显示错误信息,例如:
    <input type="text" [(ngModel)]="product.name" name="name" required>
    <div *ngIf="orderForm.get('products').get(i).get('name').hasError('required') && (orderForm.get('products').get(i).get('name').touched || orderForm.get('products').get(i).get('name').dirty)">
      商品名称是必填项
    </div>
    

3. 核心代码示例

  1. 组件类代码(TypeScript)
    import { Component } from '@angular/core';
    import { FormGroup, FormControl, FormArray, Validators, AbstractControl } from '@angular/forms';
    
    export function quantityValidator(control: AbstractControl): { [key: string]: any } | null {
      const quantity = control.get('quantity');
      if (quantity && quantity.value < 0) {
        return { negativeQuantity: true };
      }
      return null;
    }
    
    @Component({
      selector: 'app-order-form',
      templateUrl: './order - form.component.html',
      styleUrls: ['./order - form.component.css']
    })
    export class OrderFormComponent {
      orderForm: FormGroup;
    
      constructor() {
        this.orderForm = new FormGroup({
          products: new FormArray([])
        });
        this.addProduct();
      }
    
      addProduct() {
        const productFormGroup = new FormGroup({
          name: new FormControl('', Validators.required),
          quantity: new FormControl(0, [Validators.required, Validators.min(0)]),
        }, { validators: quantityValidator });
        (this.orderForm.get('products') as FormArray).push(productFormGroup);
      }
    
      onSubmit() {
        if (this.orderForm.valid) {
          console.log('表单提交成功', this.orderForm.value);
        } else {
          this.validateAllFormFields(this.orderForm);
        }
      }
    
      validateAllFormFields(formGroup: FormGroup) {
        Object.keys(formGroup.controls).forEach(field => {
          const control = formGroup.get(field);
          if (control instanceof FormControl) {
            control.markAsTouched({ onlySelf: true });
          } else if (control instanceof FormGroup) {
            this.validateAllFormFields(control);
          } else if (control instanceof FormArray) {
            control.controls.forEach(c => {
              if (c instanceof FormGroup) {
                this.validateAllFormFields(c);
              }
            });
          }
        });
      }
    }
    
  2. 模板代码(HTML)
    <form [formGroup]="orderForm">
      <div formArrayName="products">
        <div *ngFor="let product of orderForm.get('products').controls; let i = index" [formGroupName]="i">
          <h3>商品 {{i + 1}}</h3>
          <input type="text" formControlName="name" placeholder="商品名称" required>
          <div *ngIf="orderForm.get('products').get(i).get('name').hasError('required') && (orderForm.get('products').get(i).get('name').touched || orderForm.get('products').get(i).get('name').dirty)">
            商品名称是必填项
          </div>
          <input type="number" formControlName="quantity" placeholder="商品数量" required>
          <div *ngIf="orderForm.get('products').get(i).get('quantity').hasError('required') && (orderForm.get('products').get(i).get('quantity').touched || orderForm.get('products').get(i).get('quantity').dirty)">
            商品数量是必填项
          </div>
          <div *ngIf="orderForm.get('products').get(i).hasError('negativeQuantity') && (orderForm.get('products').get(i).touched || orderForm.get('products').get(i).dirty)">
            商品数量不能为负数
          </div>
        </div>
      </div>
      <button type="button" (click)="addProduct()">添加商品</button>
      <button type="submit" (click)="onSubmit()" [disabled]="!orderForm.valid">提交</button>
    </form>