본문으로 건너뛰기

ADR-0008: 폼 구현 수단으로 Signal Forms를 채택한다

상태

Accepted

날짜

2026-08-10

맥락

프론트엔드 도메인 로직의 상당 부분이 폼에 있습니다. 입력 검증, 조건부 활성화, 서버 검증 결과 반영이 모두 여기에 모입니다.

이미 확정한 결정들이 시그널을 전제로 하고 있습니다. 서버 상태는 httpResource로 조회하고, 컴포넌트 상태는 signal로 관리하며, 변경 감지는 zoneless 방식입니다. 여기에 RxJS 기반인 Reactive Forms(FormGroup, FormControl)를 쓰면 두 반응성 모델이 한 화면에 공존하게 됩니다. 값 변화를 시그널 계산식에 넣으려면 toSignal 변환을 거쳐야 하고, 변환 지점마다 구독 수명 관리가 따라붙습니다.

Angular 22에서 @angular/forms/signals 진입점이 제공됩니다. 확인 결과 28개 API가 @publicApi 22.0으로 표기되어 있으며 실험 표기는 provideExperimentalWebMcpForms 하나뿐입니다.

결정

폼 구현에 Signal Forms(@angular/forms/signals)를 사용합니다. Reactive Forms의 신규 사용을 금지합니다.

검증 규칙은 스키마 하나에 모으고 해당 슬라이스의 model 세그먼트에 둡니다. 조건부 활성화(disabled), 표시 여부(hidden), 읽기 전용(readonly)도 스키마에 둡니다. 템플릿에서 조건식으로 계산하면 규칙이 두 곳으로 나뉩니다.

서버에 물어야 하는 검증은 validateHttp를 사용하며 debounce를 함께 지정합니다.

@angular/forms/signals/compatControlValueAccessor 기반 서드파티 컴포넌트와 연결할 때만 사용하고, 사용 시 사유를 코드 주석으로 남깁니다.

세부 규칙은 폼과 검증이 원본입니다.

검토한 대안

Reactive Forms 유지

구분 내용
장점 성숙하고 자료가 풍부합니다. 모든 서드파티 입력 컴포넌트와 바로 맞물립니다. 팀 학습 비용이 없습니다
단점 RxJS 기반이라 시그널 흐름과 섞여 변환 계층이 생깁니다. Angular가 Signal Forms 방향으로 이동하고 있어 장기적으로 재작성 대상이 됩니다
기각 사유 한 화면에 두 반응성 모델이 공존하면 상태의 원본이 어디인지 판별하기 어려워집니다. 예측 가능성이 1순위 목표인 표준에서 이 혼재는 비용이 큽니다

Signal Forms + 외부 스키마 라이브러리(zod 등)

구분 내용
장점 Signal Forms가 Standard Schema를 지원하므로 연결이 가능합니다. 스키마를 폼 외의 용도로도 재사용할 수 있고 런타임 검증까지 한 벌로 해결됩니다
단점 의존성이 늘고 Angular 내장 검증기와 외부 스키마 두 체계를 모두 알아야 합니다. 어느 쪽으로 쓸지 판정 기준을 별도로 정해야 합니다
기각 사유 판정이 들어가는 규칙은 예측 가능성을 깎습니다. 서버 응답 타입은 이미 OpenAPI에서 생성하므로 런타임 검증 필요성이 낮습니다. 필요해지면 신규 ADR로 판단합니다

결과

폼 상태가 다른 상태와 같은 반응성 모델을 갖게 되어 변환 계층이 사라집니다. 검증 규칙의 위치가 스키마 하나로 고정되어 배치 판정이 필요 없습니다. 서버 검증 연동이 validateHttp로 프레임워크 안에서 해결됩니다.

감수하는 사항은 다음과 같습니다.

  • 새 API라 사례와 참고 자료가 적습니다. 문제 상황에서 검색으로 해결하기 어려울 수 있습니다.
  • 기존 Reactive Forms 코드와의 경계가 필요할 수 있습니다. ControlValueAccessor 기반 서드파티 컴포넌트를 쓰게 되면 signals/compat 계층을 거칩니다. Spartan helm은 해당하지 않습니다.
  • 기존 Reactive Forms 코드가 있는 프로젝트는 혼재 상태를 거칩니다. 본 표준은 신규 사용만 금지하며 일괄 전환을 요구하지 않습니다.

개정

  • 2026-08-11: Spartan helm 입력 컴포넌트가 Signal Forms와 직접 연결됨을 확인했습니다. compat 계층이 필요하지 않습니다. 컨트롤은 [formField]로, <form>[formRoot]로 바인딩하며 오류는 필드의 errors() 시그널에서 읽습니다. 결과 절의 해당 항목을 완화했습니다. 결정 자체는 바뀌지 않으므로 신규 ADR을 발행하지 않습니다.

확인이 필요한 항목

항목 확인할 내용
validateHttp의 요청 취소 값이 빠르게 바뀔 때 이전 요청이 취소되는지
© 2026 dev.goraebap