앵귤러 21 시그널 폼: 시그널 API가 아키텍처 차원에서 폼 관리를 어떻게 혁신하는가
앵귤러 21은 단순한 새로운 유틸리티가 아니라, 폼 상태 관리의 패러다임을 근본적으로 전환한 시그널 폼을 도입했습니다. 이는 반응형 폼의 진화가 아니라, 시그널 API라는 렌즈를 통해 재구상된 것입니다: 모델과 UI 간의 직접적인 동기화, 컨텍스트를 잃지 않는 엄격한 타이핑, 폼 요소에 대한 선언적 상태 관리, 그리고 디바운스와 교차 필드 검증에 대한 내장 지원입니다. 아직 기능은 실험적이지만, 그 아키텍처적 솔루션은 예측 가능성과 유지보수성에 대한 높은 요구를 가진 기업 애플리케이션의 방향을 이미 제시하고 있습니다.
아키텍처 기반: FieldTree와 FieldState 대신 FormGroup/FormControl
시그널 폼은 AbstractControl 추상을 버리고 두 가지 엄격히 타입된 프리미티브로 대체합니다:
FieldTree<T>— 폼 트리를 시그널로 표현하는 프록시 객체. 각 노드는Signal<FieldState<T>>이며, 값에 접근하려면()호출을 사용합니다.FieldState<T>— 특정 필드의 상태: 값, 상태(valid,touched,dirty), 오류, 그리고 관리 메서드(set,reset,markAsTouched)입니다.
이렇게 하면 입력 데이터(@Input)를 내부 폼 상태와 수동으로 동기화할 필요가 없습니다. 반응형 폼에서는 valueChanges와 patchValue를 함께 사용해야 했는데, 이는 비동기화 위험과 복잡한 라이프사이클 관리를 초래했습니다. 반면 시그널 폼에서는 모델이 바로 단일 진실의 원천이 됩니다:
export interface LoginFormModel {
email: string;
password: string;
}
@Component({
imports: [Field],
template: `
<form>
Email: <input [field]="loginForm.email">
Password: <input [field]="loginForm.password">
</form>
`
})
export class LoginForm {
login = model.required<LoginFormModel>();
loginForm = form(this.login); // 모델 시그널을 폼 트리에 연결
}
여기서 this.login은 WritableSignal<LoginFormModel>이며, loginForm.email()은 FieldState<string>를 반환합니다. login.set({...})를 변경하면 연결된 모든 FieldState가 즉시 업데이트되고, 반대로 loginForm.email().set(...)로 값을 수정하면 login()도 업데이트됩니다.
타이핑을 잃지 않음: AbstractControl에서 정밀한 필드 타입으로
핵심 장점은 모든 수준에서 타입 구조를 보존한다는 점입니다. 반응형 폼에서는 get('nested.field')가 AbstractControl | null을 반환해 중첩 구조에 대한 정보를 잃어버립니다. 반면 시그널 폼에서는 경로 path.additionalInformation.firstName가 FieldState<string>라는 엄격한 타입을 가지며, path.additionalInformation()는 FieldState<{ firstName: string; lastName: string; }>. 이 덕분에 effect, computed 등 시그널 API의 프리미티브를 타입 캐스팅이나 undefined 확인 없이 안전하게 사용할 수 있습니다.
예를 들어 additionalInformation 그룹을 살펴보겠습니다:
effect(() => {
const info = this.loginForm.additionalInformation();
console.log(info.firstName().value()); // string — 컴파일러가 정확히 알 수 있음
console.log(info.lastName().touched()); // boolean
});
이런 타이핑은 고전적인 버그를 없애줍니다: null에 .value를 호출하려는 시도, 중첩 FormGroup 작업 시 발생하는 오류, 그리고 get().get().value와 같은 체인에서 암묵적으로 발생하는 any 문제를 해결합니다.
요소 상태 관리: hidden, disabled, readonly
시그널 폼은 세 가지 내장 메커니즘을 제공하여 가시성과 상호작용성을 제어하며, 각각 고유한 의미를 지닙니다:
hidden(path, condition?)— 해당 필드를 DOM에서 완전히 제거하고(@if를 통해) 검증 트리에서도 제외합니다. 필드가 폼에 존재하지 않으므로touched/dirty에도 영향을 미치지 않습니다.disabled(path, condition?)— 필드를 편집 불가로 만들고, 검증 및touched/dirty상태에서도 제외합니다.readonly(path, condition?)— 편집을 차단하지만 검증 주기에 포함시키고touched/dirty를 추적할 수 있도록 합니다.
조건은 { valueOf } 컨텍스트를 받는 함수로 정의됩니다. 예를 들어:
form<{ email: string; password: string }>(
{ email: '', password: '' },
(path) => {
disabled(path.password, ({ valueOf }) => !valueOf(path.email));
}
);```
이는 `password.disabled = !email.value`와 동일하지만, 반응적으로 실행되어 일관성을 보장합니다.
## 디바운스와 검증: RxJS 대신 내장 솔루션
데이터 업데이트의 빈도를 제어하기 위해 `debounce` 함수가 구현되었으며, 밀리초 단위의 타임아웃 또는 맞춤형 `Debouncer`를 받아들입니다:
debounce(path, (
{ valueOf },
abortSignal
) => {
return new Promise<void>((resolve) => {
const timeout = setTimeout(() => resolve(), valueOf(path.email).length * 100);
abortSignal.addEventListener('abort', () => clearTimeout(timeout));
});
});```
검증은 동기식(required, minLength, pattern)과 비동기식(validateAsync, validateHttp)으로 나뉩니다. 특별한 기능으로는 validateTree 지원이 있는데, 이를 통해 교차 필드 규칙을 만들 수 있습니다. 예를 들어 비밀번호 일치 여부를 확인하는 경우:
validateTree(path, (ctx) => {
const { password, confirmPassword } = ctx.value();
if (password !== confirmPassword) {
return {
field: ctx.fieldTree.confirmPassword,
kind: 'passwordMismatch',
message: '비밀번호가 일치하지 않습니다'
};
}
});```
여기서 `ctx.fieldTree`는 트리 내 모든 `FieldState`에 접근할 수 있으며, `ctx.value()`는 전체 폼의 현재 값을 제공합니다.
## 무엇이 중요한가
- 시그널 폼은 반응형 폼을 대체하는 것이 아니라, 시그널 API 아래에서 이루어진 아키텍처적 대혁신입니다: 이제 모델과 폼이 단일 진실의 원천이 됩니다.
- 모든 중첩 수준에서 타이핑이 보존되어 `AbstractControl`과 수동 `instanceof` 확인이 필요 없게 됩니다.
- `hidden`, `disabled`, `readonly`는 명확한 의미를 가지고 있으며 검증과 `touched/dirty` 상태를 자동으로 관리합니다.
- `debounce`와 `validateTree`는 핵심에 통합되어 기본 시나리오에서 RxJS 의존성을 없애줍니다.
- 실험적 상태이므로 API가 변경될 수 있으며, 생산 환경에서 사용하려면 리스크를 명확히 이해하고 마이그레이션 계획을 마련해야 합니다.
앵귤러 21 시그널 폼은 더 예측 가능하고 타입 안전하며 덜 번잡한 폼 관리를 위한 한 걸음입니다. 단순한 경우는 간소화하지 않지만, 복잡한 시나리오—중첩 폼, 조건부 필드, 교차 검증, 동적 동기화—에서는 복잡성을 근본적으로 줄여줍니다. 중급/고급 개발자에게 이는 ‘빠른 시작’ 도구가 아니라, `FieldTree`의 구현 수준부터 `effect`의 폼 컨텍스트 내 동작까지 깊이 연구할 만한 아키텍처 프리미티브입니다.
— Editorial Team
아직 댓글이 없습니다.