심화 가이드
폼

다른 폼 구현 방식과 비교하기

Angular는 폼을 만드는 세 가지 방식으로 Signal Forms, 반응형 폼, 템플릿 기반 폼을 제공합니다. 각 방식은 상태, 유효성 검사, 데이터 흐름을 관리하는 패턴이 다릅니다. 이 가이드에서는 세 방식의 차이를 이해하고 프로젝트에 알맞은 방식을 선택하는 방법을 알아봅니다.

한눈에 비교하기

기능 Signal Forms 반응형 폼 템플릿 기반 폼
원본 데이터 사용자가 정의한 쓰기 가능한 시그널 모델 FormControl/FormGroup 컴포넌트의 사용자 모델
타입 안전성 모델에서 추론 타입이 지정된 폼으로 명시 제한적
유효성 검사 경로 기반 검증 함수를 사용하는 스키마 컨트롤에 전달하는 검증 함수 목록 디렉티브 기반
상태 관리 시그널 기반 옵저버블 기반 Angular가 관리
구성 시그널과 스키마 함수 FormControl 트리 템플릿의 NgModel
적합한 용도 시그널 기반 애플리케이션 복잡한 폼 간단한 폼
학습 난이도 보통 보통~높음 낮음
상태 안정화(v22 이상) 안정화 안정화

예제로 비교하기: 로그인 폼

세 방식으로 같은 폼을 구현한 예제를 보면 차이를 가장 쉽게 이해할 수 있습니다.

차이점 이해하기

세 방식은 설계가 다르기 때문에 폼을 작성하고 유지보수하는 방식에도 차이가 있습니다. 이런 차이는 폼 상태를 어디에 저장하고 유효성 검사를 어떻게 관리하는지에서 비롯됩니다.

폼 데이터를 저장하는 위치

가장 근본적인 차이는 폼 값의 원본 데이터(source of truth)를 어디에 두는지입니다.

Signal Forms는 값을 쓸 수 있는 시그널에 데이터를 저장합니다. 현재 폼 값이 필요하면 시그널을 실행합니다:

const credentials = this.loginModel(); // { email: '...', password: '...' }

이 방식은 폼 데이터를 하나의 반응형 컨테이너에 저장하고, 값이 변경되면 Angular에 자동으로 알립니다. 폼 구조는 데이터 모델과 정확히 일치합니다.

반응형 폼은 FormControl과 FormGroup 인스턴스에 데이터를 저장합니다. 폼 계층을 통해 값에 접근합니다:

const credentials = this.loginForm.value; // { email: '...', password: '...' }

이 방식은 폼 상태 관리를 컴포넌트의 데이터 모델과 분리합니다. 폼 구조를 명시적으로 정의하지만, 설정 코드가 더 많이 필요합니다.

템플릿 기반 폼은 컴포넌트 프로퍼티에 데이터를 저장합니다. 값에 직접 접근합니다:

const credentials = {email: this.email, password: this.password};

가장 직접적인 방식이지만, 여러 값을 한데 모아야 할 때는 직접 구성해야 합니다. Angular는 템플릿의 디렉티브를 통해 폼 상태를 관리합니다.

유효성 검사 방식

각 방식은 유효성 검사 규칙을 정의하는 방법이 다릅니다. 이에 따라 유효성 검사 로직의 위치와 유지보수 방법도 달라집니다.

Signal Forms는 스키마 함수에서 필드 경로에 검증 함수를 연결합니다:

loginForm = form(this.loginModel, (fieldPath) => {
  required(fieldPath.email, {message: 'Email is required'});
  email(fieldPath.email, {message: 'Enter a valid email address'});
});

모든 유효성 검사 규칙을 한곳에 모아 정의합니다. 스키마 함수는 폼을 생성할 때 한 번 실행되고, 필드 값이 변경되면 검증 함수가 자동으로 실행됩니다. 오류 메시지도 유효성 검사 정의에 포함합니다.

반응형 폼은 컨트롤을 생성할 때 검증 함수를 연결합니다:

loginForm = new FormGroup({
  email: new FormControl('', [Validators.required, Validators.email]),
});

검증 함수는 폼 구조의 개별 컨트롤에 연결됩니다. 따라서 유효성 검사 로직이 폼 정의 곳곳에 나뉘어 배치됩니다. 오류 메시지는 일반적으로 템플릿에 작성합니다.

템플릿 기반 폼은 템플릿에서 디렉티브 어트리뷰트를 사용합니다:

<input [(ngModel)]="email" required email />

유효성 검사 규칙은 템플릿의 HTML과 함께 작성합니다. 유효성 검사 로직을 UI 가까이에 둘 수 있지만, 로직이 템플릿과 컴포넌트에 나뉘어 배치됩니다.

타입 안전성과 자동완성

각 방식은 TypeScript와 연동하는 방법이 크게 다르므로, 컴파일러가 오류를 방지하는 데 도움을 주는 정도도 다릅니다.

Signal Forms는 모델 구조에서 타입을 추론합니다:

const loginModel = signal({email: '', password: ''});
const loginForm = form(loginModel);
// TypeScript는 loginForm.email이 존재하며 FieldState<string>을 반환한다는 것을 알 수 있습니다.

시그널에서 데이터 구조를 한 번 정의하면 TypeScript가 필드의 존재 여부와 타입을 자동으로 알 수 있습니다. 존재하지 않는 loginForm.username에 접근하면 타입 오류가 발생합니다.

반응형 폼은 타입이 지정된 폼을 사용해서 타입을 명시적으로 지정해야 합니다:

const loginForm = new FormGroup({
  email: new FormControl<string>(''),
  password: new FormControl<string>(''),
});
// TypeScript는 loginForm.controls.email의 타입이 FormControl<string>이라는 것을 알 수 있습니다.

각 컨트롤의 타입을 개별적으로 지정합니다. TypeScript가 폼 구조를 검사하지만, 타입 정보는 데이터 모델과 별도로 관리해야 합니다.

템플릿 기반 폼은 제한적인 타입 안전성만 제공합니다:

email = '';
password = '';
// TypeScript는 문자열이라는 것만 알 수 있으며, 폼 수준의 타입 정보는 없습니다.

TypeScript는 컴포넌트 프로퍼티를 이해하지만, 폼 구조나 유효성 검사 정보는 알지 못합니다. 따라서 폼 관련 작업을 컴파일 시점에 검사할 수 없습니다.

구현 방식 선택하기

Signal Forms가 적합한 경우

  • 시그널 기반의 새 애플리케이션을 만드는 경우(Angular v22 이상)
  • 모델 구조에서 타입 안전성을 추론하고 싶은 경우
  • 스키마 기반 유효성 검사 방식을 선호하는 경우
  • 팀원들이 시그널에 익숙한 경우

반응형 폼이 적합한 경우

  • 운영 환경에서 검증된 안정성이 필요한 경우
  • 복잡하고 동적인 폼을 만드는 경우
  • 옵저버블 기반 패턴을 선호하는 경우
  • 폼 상태를 세밀하게 제어해야 하는 경우
  • 기존 반응형 폼 코드를 유지보수하는 경우

템플릿 기반 폼이 적합한 경우

  • 로그인, 문의, 검색과 같은 간단한 폼을 만드는 경우
  • 프로토타입을 빠르게 만드는 경우
  • 폼 로직이 단순한 경우
  • 폼 로직을 템플릿에 두는 방식을 선호하는 경우
  • 기존 템플릿 기반 폼 코드를 유지보수하는 경우

다음 단계

각 방식을 자세하게 알아보려면 다음 문서를 참고하세요: