Zod를 Angular Signal Forms와 함께 사용하는 방법 (단계별 마이그레이션)
Source: Dev.to
문제: Signal Forms 마이그레이션 후 Zod 검증을 다시 통합해야 함
많은 개발자들이 바로 이 문제를 겪습니다. Reactive Forms와 Zod 검증은 정상적으로 동작했지만, Signal Forms로 마이그레이션한 뒤 검증이 작동하지 않게 됩니다. 필드가 유효하지 않아도 폼이 제출되고, 오류 메시지가 사라집니다.
Zod는 Signal Forms의 검증 시스템을 사용해 다시 통합해야 합니다. 먼저 관련 요소들을 이해하고, 그 다음에 올바르게 마이그레이션합니다.
Zod란? Angular 개발자가 검증에 사용하는 이유
Zod가 처음이라면 간단히 살펴보세요:
- 타입 안전성 – TypeScript 타입이 검증 스키마와 동기화됩니다.
- 런타임 검증 – 컴파일 타임에 TypeScript가 잡지 못하는 오류를 포착합니다.
- 깨끗한 오류 메시지 – 기본 제공되는 인간 친화적인 검증 오류.
- 조합 가능한 스키마 – 단순한 빌딩 블록으로 복잡한 검증 규칙을 구성합니다.
Angular 개발자에게 Zod는 Angular 폼 시스템과 별개로 검증 로직을 중앙 집중화해, 프론트엔드와 백엔드 간에 규칙을 공유하거나 애플리케이션의 다양한 부분에서 스키마를 재사용하기 쉽게 해줍니다.
다른 npm 패키지처럼 Zod를 설치합니다:
npm install zod
(이미 설치되어 있다고 가정하고, 바로 코드로 넘어갑니다.)
Angular 폼 검증을 위한 Zod 스키마 이해하기
Schema file (form.schema.ts)
import { z } from 'zod';
타입 추론
export type SignupModel = z.infer;
오류 맵 타입
export type ZodErrorMap = Record;
검증 스키마
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에 표시할 수 있습니다.
마이그레이션 전 Angular Reactive Forms에 Zod를 연결하는 방법
템플릿 (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);
// Store Zod validation errors
zodErrors: ZodErrorMap = {};
// Reactive form definition
form = this.fb.group({
username: ['', [Validators.required]],
email: ['', [Validators.required, Validators.email]],
});
// Helper to retrieve errors for a specific field
getZodErrors(field: keyof SignupModel): string[] {
return this.zodErrors[field] ?? [];
}
// Submit handler
onSubmit(): void {
const value = this.form.value as SignupModel;
const result = validateSignup(value);
if (result.success) {
// Handle successful submission (e.g., send to server)
console.log('Form data is valid:', result.data);
} else {
// Populate Zod errors so the template can display them
this.zodErrors = result.errors;
// Optionally mark controls as touched to trigger UI feedback
this.form.markAllAsTouched();
}
}
}
zodErrors는validateSignup이 반환한 오류 맵을 보관합니다.getZodErrors는 템플릿에서 필드별 메시지를 가져오는 데 사용됩니다.- 제출 시 컴포넌트는 Zod로 폼 데이터를 검증하고,
zodErrors를 업데이트한 뒤, 오류 메시지가 나타나도록 컨트롤을 터치된 상태로 표시합니다.
다음 단계(여기서는 생략)는 Angular Signal Forms 설정으로 교체하고, validateTree()를 사용해 Zod의 오류 맵을 Signal Forms 검증 API에 연결하는 작업을 포함합니다.