Angular에서 ControlValueAccessor 쉽게 이해하기
Angular에서 사용자 정의 폼 컴포넌트를 만들다 보면, Angular의 폼 시스템과 연동해야 하는 경우가 있다. 그때 필요한 것이 바로 ControlValueAccessor이다. 이 인터페이스를 사용하면 Angular 폼 제어기(FormControl, NgModel)가 커스텀 컴포넌트와 원활하게 연결될 수 있다. 쉽게 말해, Angular의 기본 폼 요소(input, select 등)처럼 커스텀 컴포넌트도 동일하게 동작하도록 만들어 주는 역할을 한다.
Angular에서 커스텀 폼 컴포넌트가 Angular 폼 제어기(FormControl, NgModel)와 상호작용하는 과정은 ControlValueAccessor의 메서드들을 통해 이루어진다. 이 메서드들이 호출되는 순서를 보면 쉽게 이해할 수 있다.
- 폼 컨트롤 초기화
- 폼이 처음 렌더링될 때 Angular 폼 제어기가 연결된 커스텀 컴포넌트에 값을 전달한다.
- 이때 호출되는 메서드가 writeValue(value)이다. Angular 폼이 가진 초기 값이 컴포넌트에 설정된다.
- 값 변경 시
- 사용자가 커스텀 폼 컴포넌트에서 값을 입력하면, 해당 값이 컴포넌트 내부에서 변경된다.
- 이때 커스텀 컴포넌트는 registerOnChange(fn)으로 등록된 함수를 호출하여 Angular 폼 제어기에 값 변경을 알린다. Angular는 이 함수로 값을 받아 폼의 상태를 업데이트한다.
- 터치 이벤트
- 사용자가 컴포넌트와 상호작용하여 폼 필드가 '터치'되면, 즉 포커스가 갔다가 나오는 경우 registerOnTouched(fn)으로 등록된 콜백이 호출된다. Angular 폼 제어기는 이를 통해 폼 필드가 터치되었음을 알 수 있다.
- 폼 비활성화
- Angular 폼에서 커스텀 컴포넌트를 비활성화하거나 활성화하는 경우, setDisabledState(isDisabled) 메서드가 호출된다. 이 메서드는 컴포넌트의 비활성화 여부를 받아서 이를 반영하도록 한다.
ControlValueAccessor 구현
import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
@Component({
selector: 'app-custom-input',
template: `
<input type="text" [value]="value" (input)="onInput($event)" (blur)="onBlur()">
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
export class CustomInputComponent implements ControlValueAccessor {
value: string = '';
// 저장된 onChange와 onTouched 콜백 함수
private onChange: (value: string) => void = () => {};
private onTouched: () => void = () => {};
// 1. writeValue - 폼에서 전달된 값을 컴포넌트에 설정
writeValue(value: string): void {
this.value = value;
}
// 2. registerOnChange - 폼에서 값이 변경될 때 호출할 콜백 등록
registerOnChange(fn: any): void {
this.onChange = fn;
}
// 3. registerOnTouched - 폼에서 필드가 터치되었을 때 호출할 콜백 등록
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
// 4. setDisabledState - 폼의 활성/비활성 상태를 반영
setDisabledState(isDisabled: boolean): void {
// 필요에 따라 컴포넌트의 활성화/비활성화 처리
}
// 입력 값이 변경될 때 호출되는 함수
onInput(event: Event): void {
const value = (event.target as HTMLInputElement).value;
this.value = value;
this.onChange(value); // 폼에 값 변경을 알림
}
// 입력 필드가 터치되었을 때 호출
onBlur(): void {
this.onTouched(); // 폼에 터치 상태를 알림
}
}
//사용자 정의 컴포넌트 사용
<form #myForm="ngForm">
<app-custom-input name="customInput" [(ngModel)]="myValue"></app-custom-input>
</form>
<p>Current value: {{ myValue }}</p>
값이 입력되고 부모로 전달되는 흐름
1. 사용자가 입력 필드에 값을 입력
사용자가 커스텀 입력 컴포넌트에서 값을 입력하면, 그 값은 컴포넌트 내부의 onInput 함수에서 처리된다. 이 함수는 (input) 이벤트와 연결되어 있어, 입력이 일어날 때마다 호출된다.
2. onInput에서 값 변경 처리
onInput 함수는 사용자가 입력한 값을 받아서 컴포넌트의 value 변수에 저장한 후, this.onChange(value)를 호출한다. 여기서 onChange는 registerOnChange 메서드를 통해 Angular 폼 시스템에서 등록한 콜백 함수다.
onInput(event: Event): void {
const value = (event.target as HTMLInputElement).value;
this.value = value;
this.onChange(value); // 이 부분이 부모인 Angular 폼에 값을 전달하는 핵심
}
3. registerOnChange에서 콜백 등록
Angular 폼 시스템은 커스텀 컴포넌트와 상호작용하기 위해, registerOnChange 메서드를 호출하면서 콜백 함수를 등록한다. 이 함수는 값이 변경될 때마다 실행되도록 커스텀 컴포넌트에서 관리되며, onChange 콜백이 호출되면 Angular는 내부적으로 폼의 값을 갱신한다.이 과정을 통해, 컴포넌트에서 값이 변경될 때마다 fn, 즉 Angular의 폼 시스템에 등록된 함수가 호출되고, 이 함수는 폼 전체의 상태를 업데이트한다.
registerOnChange(fn: any): void {
this.onChange = fn;
}
4. 부모(Angular 폼)가 값을 받음
결국 this.onChange(value) 호출 시 등록된 함수가 호출되면, Angular 폼 시스템은 새로운 값을 감지하고 해당 폼 컨트롤의 값을 업데이트한다. 즉, 부모 컴포넌트나 폼 그룹, 폼 배열 등 Angular의 폼 구조가 이 값을 반영하게 된다.
결론
요약하자면, 커스텀 입력 컴포넌트에서 값이 변경되면:
- 사용자가 입력한 값이 onInput 함수를 통해 잡힌다.
- 그 값은 onChange 콜백 함수로 전달되고,
- 이 콜백 함수는 registerOnChange로 Angular 폼 시스템에 등록된 함수로서 폼의 상태를 업데이트한다.
- 최종적으로 부모 컴포넌트나 폼 컨트롤이 해당 값의 변화를 감지하고 업데이트된다.
'Angular' 카테고리의 다른 글
Angular - i18next를 사용한 번역 설정 (0) | 2024.11.26 |
---|---|
Angular - 뒤로 가기 방지 관련 이슈 및 해결 방법 (history) (2) | 2024.11.01 |
Angular - 동작원리 및 RxJS와 Ngrx (0) | 2024.08.22 |
Angular Google Maps (AGM) 사용법 (1) | 2024.07.11 |
Angular 유닛 테스트: Jasmine vs Jest 비교 (0) | 2023.05.23 |