NgModel
, FormControl
, FormGroup
과 같은 폼 API와 커스텀 폼 컨트롤을 통합할 수 있다.ControlValueAccessor
는 다음과 같은 메서드들을 포함한다.
writeValue(obj: any): void
registerOnChange(fn: any): void
registerOnTouched(fn: any): void
setDisabledState?(isDisabled: boolean): void
예를 들어, 커스텀 입력 컴포넌트를 만들고 이를 Angular 폼과 함께 사용하고자 할 때 ControlValueAccessor
를 구현할 수 있다.
ControlValueAccessor
interface 확장import { ControlValueAccessor } from '@angular/forms';
export class InputTextComponent implements ControlValueAccessor {}
@Component({
...
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputTextComponent),
multi: true
}
]
})
import {forwardRef} from '@angular/core';
import { NG_VALIDATORS } from '@angular/forms';
export const provideValueAccessor = (component: any) => {
return {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => component),
multi: true,
};
};
@Component({
providers: [provideValueAccessor(InputTextComponent)],
})
ControlValueAccessor
메서드 정의export class InputTextComponent implements ControlValueAccessor {
value = signal<string | null>('');
// 외부에서 무언가 변경되면 이 컴포넌트에서 무엇을 해야 할지 정의
writeValue(value: string | null): void {
this.value.set(value);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
onChange = (value: any) => {};
onTouched = (value: any) => {};
setDisabledState?(isDisabled: boolean): void {}
}
toObservable
을 사용하여 value
신호(signal)를 옵저버블로 변환한 후, constructor
에서 옵저버블을 구독하여 값의 변화를 처리한다.import {
Component,
forwardRef,
signal,
viewChild,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { distinctUntilChanged } from 'rxjs';
import { toObservable } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-input-text',
standalone: true,
imports: [],
templateUrl: './input-text.component.html',
styleUrl: './input-text.component.scss',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputTextComponent),
multi: true,
},
],
})
export class InputTextComponent implements ControlValueAccessor {
value = signal<string | null>('');
constructor() {
toObservable(this.value)
.pipe(distinctUntilChanged())
.subscribe((value) => this.onChange(value));
}
writeValue(value: string | null): void {
this.value.set(value);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
onChange = (value: any) => {};
onTouched = (value: any) => {};
setDisabledState?(isDisabled: boolean): void {}
}
<input
[ngModel]="value()"
(ngModelChange)="onChange($event)"
/>
AfterViewInit
라이프사이클 훅에서 fromEvent
를 사용하여 input
DOM 이벤트를 직접 관찰한다.viewChild
를 사용하여 DOM 요소에 접근.import {
AfterViewInit,
Component,
ElementRef,
forwardRef,
signal,
viewChild,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { distinctUntilChanged, fromEvent } from 'rxjs';
@Component({
selector: 'app-input-text',
standalone: true,
imports: [],
templateUrl: './input-text.component.html',
styleUrl: './input-text.component.scss',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputTextComponent),
multi: true,
},
],
})
export class InputTextComponent implements ControlValueAccessor, AfterViewInit {
inputRef = viewChild.required<ElementRef<HTMLInputElement>>('inputRef');
value = signal<string | null>('');
ngAfterViewInit(): void {
fromEvent(this.inputRef().nativeElement, 'input')
.pipe(distinctUntilChanged())
.subscribe((event: any) => {
this.value.set(event.target.value);
this.onChange(event.target.value);
});
}
writeValue(value: string | null): void {
this.value.set(value);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
onChange = (value: any) => {};
onTouched = (value: any) => {};
setDisabledState?(isDisabled: boolean): void {}
}
<input #inputRef [ngModel]="value()" (blur)="onChange(value())" />
[추가] (blur)
이벤트 바인딩
(blur)
는 Angular의 이벤트 바인딩 문법으로, blur
이벤트가 발생할 때 지정된 메서드를 호출blur
이벤트는 사용자가 입력 필드를 클릭한 후 다른 요소를 클릭하여 포커스를 잃을 때 발생(blur)="onChange(value())"
를 input
요소에 추가하면, 입력 필드에서 포커스가 벗어났을 때 onChange
메서드가 호출된다. 이때 value()
를 인수로 전달하여 현재 입력 값을 전달하게 된다.import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
form = new FormGroup({
customInput: new FormControl('')
});
}
<form [formGroup]="form">
<app-input-text formControlName="customInput"></app-input-text>
</form>
References
Understanding angular ControlValueAccessor with an example
www.tsmean.com