홈으로 돌아가기

Angular 21 Signal Forms: 아키텍처와 타이핑

이 기사는 Angular 21의 Signal Forms를 상세히 설명합니다: 아키텍처 기본 요소 FieldTree와 FieldState, 타이핑 장점, 요소 상태 관리 (Hidden/Disabled/Readonly), 내장 Debounce 및 ValidateTree를 통한 크로스-필드 검증.

Angular 21: 추상화 없는 Signal Forms
Advertisement 728x90

앵귤러 21 시그널 폼: 시그널 API가 아키텍처 차원에서 폼 관리를 어떻게 혁신하는가

앵귤러 21은 단순한 새로운 유틸리티가 아니라, 폼 상태 관리의 패러다임을 근본적으로 전환한 시그널 폼을 도입했습니다. 이는 반응형 폼의 진화가 아니라, 시그널 API라는 렌즈를 통해 재구상된 것입니다: 모델과 UI 간의 직접적인 동기화, 컨텍스트를 잃지 않는 엄격한 타이핑, 폼 요소에 대한 선언적 상태 관리, 그리고 디바운스와 교차 필드 검증에 대한 내장 지원입니다. 아직 기능은 실험적이지만, 그 아키텍처적 솔루션은 예측 가능성과 유지보수성에 대한 높은 요구를 가진 기업 애플리케이션의 방향을 이미 제시하고 있습니다.

아키텍처 기반: FieldTree와 FieldState 대신 FormGroup/FormControl

시그널 폼은 AbstractControl 추상을 버리고 두 가지 엄격히 타입된 프리미티브로 대체합니다:

  • FieldTree<T> — 폼 트리를 시그널로 표현하는 프록시 객체. 각 노드는 Signal<FieldState<T>>이며, 값에 접근하려면 () 호출을 사용합니다.
  • FieldState<T> — 특정 필드의 상태: 값, 상태(valid, touched, dirty), 오류, 그리고 관리 메서드(set, reset, markAsTouched)입니다.

이렇게 하면 입력 데이터(@Input)를 내부 폼 상태와 수동으로 동기화할 필요가 없습니다. 반응형 폼에서는 valueChangespatchValue를 함께 사용해야 했는데, 이는 비동기화 위험과 복잡한 라이프사이클 관리를 초래했습니다. 반면 시그널 폼에서는 모델이 바로 단일 진실의 원천이 됩니다:

Google AdInline article slot
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.loginWritableSignal<LoginFormModel>이며, loginForm.email()FieldState<string>를 반환합니다. login.set({...})를 변경하면 연결된 모든 FieldState가 즉시 업데이트되고, 반대로 loginForm.email().set(...)로 값을 수정하면 login()도 업데이트됩니다.

타이핑을 잃지 않음: AbstractControl에서 정밀한 필드 타입으로

핵심 장점은 모든 수준에서 타입 구조를 보존한다는 점입니다. 반응형 폼에서는 get('nested.field')AbstractControl | null을 반환해 중첩 구조에 대한 정보를 잃어버립니다. 반면 시그널 폼에서는 경로 path.additionalInformation.firstNameFieldState<string>라는 엄격한 타입을 가지며, path.additionalInformation()FieldState<{ firstName: string; lastName: string; }>. 이 덕분에 effect, computed 등 시그널 API의 프리미티브를 타입 캐스팅이나 undefined 확인 없이 안전하게 사용할 수 있습니다.

예를 들어 additionalInformation 그룹을 살펴보겠습니다:

Google AdInline article slot
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 } 컨텍스트를 받는 함수로 정의됩니다. 예를 들어:

Google AdInline article slot
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

Advertisement 728x90

다음 읽기