如何在 Angular Signal Forms 中使用 Zod(逐步迁移)

发布: (2025年12月12日 GMT+8 16:00)
5 min read
原文: Dev.to

Source: Dev.to

问题描述:Signal Forms 迁移后需要重新集成 Zod 验证

许多开发者都会遇到同样的情况:表单在使用 Reactive Forms 和 Zod 验证时工作正常,但迁移到 Signal Forms 后验证失效。即使字段无效,表单仍会提交,错误信息也会消失。

需要使用 Signal Forms 的验证系统重新集成 Zod。我们先了解涉及的各个部分,然后正确地完成迁移。

什么是 Zod?为什么 Angular 开发者用它来做验证

如果你对 Zod 还不熟悉,这里有一个快速概览:

  • 类型安全 – TypeScript 类型与验证 schema 保持同步。
  • 运行时验证 – 捕获 TypeScript 编译时无法捕获的错误。
  • 简洁的错误信息 – 开箱即用的人类可读的验证错误。
  • 可组合的 schema – 可以用简单的构件块构建复杂的验证规则。

对于 Angular 开发者来说,Zod 将验证逻辑集中在 Angular 表单系统之外,便于在前后端之间共享规则或在应用的不同模块中复用 schema。

像其他 npm 包一样安装 Zod:

npm install zod

(假设已经安装好,我们可以直接进入代码。)

理解用于 Angular 表单验证的 Zod Schema

Schema 文件 (form.schema.ts)

import { z } from 'zod';

类型推断

export type SignupModel = z.infer;

错误映射类型

export type ZodErrorMap = Record;

验证 schema

export const signupSchema = z.object({
  username: z
    .string()
    .min(3, 'Username must be at least 3 characters long')
    .regex(
      /^[a-zA-Z0-9_]+$/,
      'Only letters, numbers, and underscores are allowed'
    ),
  email: z.string().email('Please enter a valid email address'),
});

验证辅助函数

export function validateSignup(value: SignupModel) {
  const result = signupSchema.safeParse(value);

  if (result.success) {
    return {
      success: true as const,
      data: result.data,
      errors: {} as ZodErrorMap,
    };
  }

  const errors = result.error.issues.reduce((acc, issue) => {
    const field = issue.path[0]?.toString() ?? '_form';
    (acc[field] ??= []).push(issue.message);
    return acc;
  }, {});

  return {
    success: false as const,
    data: null,
    errors,
  };
}

validateSignup 函数返回成功的结果(包含已验证的数据)或失败的结果(包含干净的 ZodErrorMap,我们可以在 UI 中展示)。

Zod 在 Angular Reactive Forms 中的接入方式(迁移前)

模板(Reactive Forms)

@let usernameErrors = getZodErrors('username');

@if (usernameErrors.length && form.controls.username.touched) {
  @for (err of usernameErrors; track $index) {
    - {{ err }}
  }
}

@let emailErrors = getZodErrors('email');

@if (emailErrors.length && form.controls.email.touched) {
  @for (err of emailErrors; track $index) {
    - {{ err }}
  }
}

<button type="submit">Submit</button>
  • formGroup 将模板绑定到 Reactive Form 实例。
  • formControlName 把每个输入框连接到对应的 FormControl
  • getZodErrors(fieldName) 用来获取指定字段的 Zod 错误信息数组。
  • 只有在字段已被触碰且存在错误信息时才显示错误。

组件 TypeScript:Reactive Forms 实现方式

import { Component, inject } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { ZodErrorMap, validateSignup, SignupModel } from './form.schema';

@Component({
  selector: 'app-signup',
  templateUrl: './form.component.html',
})
export class FormComponent {
  private fb = inject(FormBuilder);

  // 存放 Zod 验证错误
  zodErrors: ZodErrorMap = {};

  // Reactive Form 定义
  form = this.fb.group({
    username: ['', [Validators.required]],
    email: ['', [Validators.required, Validators.email]],
  });

  // 获取特定字段错误的辅助方法
  getZodErrors(field: keyof SignupModel): string[] {
    return this.zodErrors[field] ?? [];
  }

  // 提交处理函数
  onSubmit(): void {
    const value = this.form.value as SignupModel;
    const result = validateSignup(value);

    if (result.success) {
      // 处理成功提交(例如发送到服务器)
      console.log('Form data is valid:', result.data);
    } else {
      // 将 Zod 错误填充到模板可显示的对象中
      this.zodErrors = result.errors;
      // 可选:标记所有控件为 touched,以触发 UI 反馈
      this.form.markAllAsTouched();
    }
  }
}
  • zodErrors 保存 validateSignup 返回的错误映射。
  • getZodErrors 在模板中用于获取字段对应的错误信息。
  • 提交时,组件使用 Zod 验证表单数据,更新 zodErrors,并将控件标记为 touched,以确保错误信息出现。

接下来的步骤(此处未展示)将用 Angular Signal Forms 替换 Reactive Form 的实现,使用 validateTree() 将 Zod 的错误映射桥接到 Signal Forms 的验证 API 中。

Back to Blog

相关文章

阅读更多 »