为了账号安全,请及时绑定邮箱和手机立即绑定

Angular 19打造动态表单:用独立组件实现灵活可扩展的用户界面

表单对于大多数 web 应用程序来说至关重要,无论是注册、数据输入还是问卷。虽然 Angular 的 Reactive Forms 模块非常适合创建固定表单,但许多应用程序需要表单能够根据用户操作或外部数据的变化做出动态调整。

在这篇文章中,我们将深入探讨如何使用 Angular 19 的独立组件构建动态表单,采用模块化的方法,从而不需要传统的 Angular 模块。虽然配套的 GitHub 仓库中包含了 Tailwind CSS 来美化表单,但本文只关注动态表单的核心功能,不涉及 Tailwind 样式和配置。示例中故意不包含相关的内容,以保持重点明确。

这是一张图片,描述了...
点击这里查看图片

……

什么是动态表单?

动态表单允许你在运行时定义表单的结构(字段、验证器、布局等),而不是在编译时。这在如下情况特别有用:

  • 具有多步骤和条件跳转的表单。
  • 根据 API 响应生成的表单。
  • 用户可自定义的表单,支持动态增删字段。

独立组件的优势

独立组件通过不再依赖 NgModule 简化了 Angular 开发流程。您可以直接在组件内声明依赖(例如,响应式表单、路由),从而简化了开发流程。

  • 简化了流程。
  • 更模块化的结构。
  • 更快的开发速度。

Angular的FormArrayFormGroup使得管理这样的表单变得简单,提供了添加、移除或修改控件的灵活性,支持动态操作。

此处省略内容

一步步构建互动表格指南

1. 安装 Angular 框架并创建一个新的项目。

在我们开始深入高级示例之前,让我们先从创建一个新应用开始。

@angular/cli 的安装命令是:npm install @angular/cli

全屏模式,退出全屏

现在来创建一个新的Angular应用。对于样式表格式,可以选择SCSS,而对于SSR,选择不启用

ng new 动态表单示例应用

全屏模式,退出全屏

2.: 创建动态表单

要创建动态表单,我们使用 Angular 的 Reactive Forms 中提供的 FormGroupFormArray。以下是完整的实现代码:

组件代码

    import { Component } from "@angular/core";
    import {
      FormBuilder,
      FormGroup,
      FormArray,
      Validators,
      ReactiveFormsModule,
    } from "@angular/forms";

    @Component({
      selector: "app-root",
      standalone: true,
      imports: [ReactiveFormsModule],
      templateUrl: "./app.component.html",
      styleUrls: ["./app.component.scss"],
    })
    export class AppComponent {
      dynamicForm: FormGroup; // 主要表单

      constructor(private fb: FormBuilder) {
        this.dynamicForm = this.fb.group({
          name: [""], // 简单的输入框
          email: [""], // 另一个输入框
          fields: this.fb.array([]), // 动态字段将保存在这里
        });
      }

      // 动态字段的 FormArray 获取器
      get fields(): FormArray {
        return this.dynamicForm.get("fields") as FormArray;
      }

      /**

* 添加一个新字段到动态表单。
       */
      addField() {
        const fieldGroup = this.fb.group({
          label: [""], // 字段标签
          value: [""], // 字段值
        });
        this.fields.push(fieldGroup);
      }

      /**

* 移除动态表单中指定索引的字段。

* @param index 需要移除的字段索引。
       */
      removeField(index: number) {
        this.fields.removeAt(index);
      }

      /**

* 提交表单并将当前值打印到控制台。
       */
      submitForm() {
        console.log(this.dynamicForm.value);
      }
    }

切换到全屏 点击退出全屏

方法说明

  1. addField()

此方法会创建一个新的表单组,包含两个控件:labelvalue。并将新的表单组添加到 fields 数组中。从而使用户能够动态添加新的字段到表单中。

  1. removeField(index: number)

此方法会从 fields 数组中移除指定索引处的表单组元素。当用户希望删掉不再需要的字段时,这个方法非常有用。

  1. submitForm()

此方法获取当前表单的状态并进行记录。在实际的应用场景中,通常你会将这些数据发送到服务器或用于更新界面。

*

  • *

代码模板

打开 app.component.html,删除所有内容,并在其中添加以下模板。该模板动态显示相应的表单控件,并提供可用于添加或删除字段的按钮。

    <form [formGroup]="dynamicForm" (ngSubmit)="submitForm()">
      <div>
        <label>姓名:</label>
        <input formControlName="name" />
      </div>

      <div>
        <label>邮箱:</label>
        <input formControlName="email" />
      </div>

      <div formArrayName="fields">
        @for(field of fields.controls; let i = $index; track field) {
          <div [formGroupName]="i">
            <label>
              标签:
              <input formControlName="label" />
            </label>
            <label>
              值:
              <input formControlName="value" />
            </label>
            <button type="button" (click)="removeField(i)">删除</button>
          </div>
        }
      </div>

      <button type="button" (click)="addField()">添加</button>
      <button type="submit">提交表单</button>
    </form>

进入全屏,退出全屏

模板拆分

  1. 静态字段(“Name”“Email”

这些字段总是存在,并绑定到 formControlName

  1. 动态字段(动态)
  • @for 遍历 FormArray 以渲染每个动态字段。
  • 每个字段绑定了它的 labelvalue
  • labelvalue 是特定的属性名。
  1. 按钮
  • 点击“添加字段”按钮会调用addField()来增加一个新的动态字段。

  • 每个动态字段都有一个“删除”按钮,它会调用removeField()

此处省略内容

使用API数据构建一个动态表

在许多应用程序中,表单的结构通常来源于外部,比如存储在服务器上的配置文件。

加载表单配置信息:

假设以下 JSON 是从一个 API 返回的:

    {
      "fields": [
        { "label": "用户名", "type": "text", "required": "必填" },
        { "label": "年龄", "type": "number", "required": "非必填" },
        {
          "label": "性别", 
          "type": "select", 
          "options": ["男", "女"], 
          "required": "必填"
        }
      ]
    }

进入全屏 退出全屏

动态字段渲染

下面是如何根据此配置动态生成表格呢?

    import { Component, OnInit } from "@angular/core";
    import { FormBuilder, FormGroup, Validators } from "@angular/forms";

    @Component({
      selector: "app-dynamic-api-form",
      templateUrl: "./dynamic-api-form.component.html",
    })
    export class DynamicApiFormComponent implements OnInit {
      dynamicForm!: FormGroup; // 主要的反应式表单实例
      formConfig: any; // 从 API 获取的配置数据

      constructor(private fb: FormBuilder) {}

      ngOnInit() {
        this.fetchFormConfig().then((config) => {
          this.formConfig = config;
          this.buildForm(config.fields); // 根据配置构建表单
        });
      }

      /**

* 模拟从 API 获取数据。

* 在实际应用中,这通常是一个 HTTP 请求。
       */
      async fetchFormConfig() {
        // 模拟从 API 获取数据
        return {
          fields: [
            { label: "用户名", type: "text", required: true },
            { label: "年龄", type: "number", required: false },
            {
              label: "性别",
              type: "select",
              options: ["男性", "女性"],
              required: true,
            },
          ],
        };
      }

      /**

* 根据获取的配置动态创建表单控件。
       */
      buildForm(fields: any[]) {
        const controls: any = {};
        fields.forEach((field) => {
          const validators = field.required ? [Validators.required] : [];
          controls[field.label] = ["", validators];
        });
        this.dynamicForm = this.fb.group(controls);
      }

      /**

* 处理表单提交,将表单值打印到控制台。
       */
      submitForm() {
        console.log(this.dynamicForm.value);
      }
    }

全屏模式(进入/退出)

解析代码

特性

  1. dynamicForm
  • 持有用于反应式表单的主要 FormGroup 实例,并且根据 API 响应动态创建。
  1. formConfig
  • 从 API 获取并存储配置信息。

  • 定义字段、字段类型、验证规则,并根据需要设定选项。

此处省略内容

方法如下

  1. ngOnInit()
  • 生命周期钩子,用于在组件初始化后运行。

  • 调用 fetchFormConfig() 函数来获取表单配置信息并设置表单。

  1. fetchFormConfig()
  • 模拟了一个 API 调用来获取表单配置信息。在实际生产环境中,将这个模拟替换为实际的 HTTP 请求,以获取配置信息。

示例配置:

       {
         fields: [
           { label: "姓名", type: "text", required: true },
           { label: "年龄", type: "number", required: true },
           {
             label: "性别",
             type: "select",
             required: true,
             options: ["男性", "女性", "其他"],
           },
         ]
       }

切换到全屏 退出全屏

此处省略内容

  1. buildForm()
  • 根据获取的配置动态创建 FormGroup
  • 对于配置中的每个字段来说:

  • 添加一个 FormControlFormGroup 中。

  • 如果该字段被标记为必填,则添加相应的验证器,比如 Validators.required

示例 (FormGroup 结构):

       {
         名字: ['', [Validators.required]],
         年龄: ['', [Validators.required]],
         性别: ['', [Validators.required]]
       }

全屏模式 全屏退出


  1. 提交表单()
  • 表单提交时触发事件。
  • 表单有效时:

  • 将表单的数据记录到控制台。
  • 表单无效时:

  • 记录错误信息。

比如的结果(表单值):

       {
         名字: '约翰多伊',
         年龄: 30,
         性别: '男'
       }

进入全屏模式 全屏切换


它是怎么合作的

  1. 初始化

    当组件加载完毕时,ngOnInit() 会调用 fetchFormConfig() 以模拟获取表单结构。

  2. 建造形式
  • buildForm() 使用获取的配置来动态构建一个响应式表单。
  1. 用户互动
  • 该表单通过相关的 HTML 展示。

  • 用户可以根据字段类型输入值或选择相应选项。
  1. 验证部分
  • 表单控件强制执行验证规则,例如必填字段。

  • 触碰无效字段时会显示提示信息。
  1. 提交
  • 在点击提交按钮时,submitForm() 会检查表单的有效性,并适当地处理表单中的值。

这样就能让我们更清楚地了解TypeScript代码中每个方法和属性的功能和它们的作用。

模版:

    @if(dynamicForm) {
      <form [formGroup]="dynamicForm" (ngSubmit)="submitForm()">
        @for(field of formConfig.fields; track field) {

          <label>{{ field.label }}:</label>
          @switch(field.type) { 
            @case('text') {
              <input [formControlName]="field.label" />
            } 
            @case('number') {
              <input
                [formControlName]="field.label"
                type="number"
              />
            } 
            @case('select') {
              <select [formControlName]="field.label">
                @for(option of field.options; track option) {
                  <option [value]="option">
                    {{ option }}
                  </option>
                }
              </select>
            } 
          } 
        }
        <button type="submit">提交</button>
      </form>
    }

切换到全屏模式 恢复普通模式

HTML结构拆解

  1. @if(dynamicForm)

确保此表单仅在初始化并获取配置后才显示出来。

  1. @for(field of formConfig.fields; track field)

遍历 formConfig.fields 数组中的每个字段,从而动态生成表单控件。

  1. @switch(field.type)

根据字段配置中的 type 属性(如 textnumberselect),动态选择要渲染的表单元素。

4.: 输入框类型

  • 文本字段 (@case('text')) 渲染一个用于文本输入的 <input> 元素。
  • 数字字段 (@case('number')) 渲染一个类型为 number<input> 元素。
  • 选择字段 (@case('select')) 渲染一个 <select> 元素,从字段配置中的 options 数组动态填充选项。
  1. 验证反馈
  • @if(dynamicForm.get(field.label)?.invalid&& dynamicForm.get(field.label)?.touched && dynamicForm.get(field.label)?.hasError('required')) 只有当字段无效且已被用户操作过时,才会显示验证错误消息。
  1. 提交按钮
  • [disabled]="dynamicForm.invalid" 在所有必填字段都填写正确之前,提交按钮会被禁用。

    • *

此模板确保动态表单完全响应获取到的配置,并为必填字段提供实时验证信息。


动态验证工具

你也可以根据用户输入或根据条件实时调整输入验证器。

比如:

    onRoleChange(角色: string) {
      const emailControl = this.dynamicForm.get('email');
      if (角色 === 'admin') {
        emailControl?.setValidators([Validators.required, Validators.email]);
      } else {
        emailControl?.clearValidators();
      }
      emailControl?.updateValueAndValidity();
    }

点击全屏模式按钮 点击退出全屏按钮

总结

Angular 中的动态表单提供了一种灵活的方式来构建高度交互和可扩展的用户界面。通过利用FormArrayFormGroup和API驱动的配置,你可以创建能够适应用户需求并保持健壮性和高性能的表单。你可以从这个仓库(包含Tailwind)下载代码:https://github.com/sonukapoor/dynamic-forms-sample-app

使用这些技术来设计更智能的表单,让用户更方便,同时简化代码。祝您编码开心!

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消