IT
ControlValueAccessor Angular 사용 예시 / CustomValueAccessor
agnusdei1207
2023. 4. 24. 15:29
반응형
SMALL
import { AbstractControl, ControlValueAccessor, ValidationErrors, Validator } from "@angular/forms";
export class CustomValueAccessor<T> implements ControlValueAccessor, Validator {
disabled = false;
required: boolean = false;
touched: boolean = false;
onChange: any = (value: T) => { };
onTouched: any = () => { };
onValidationchange: any = () => { };
private _value!: T;
set value(newValue: T) {
if (this._value === newValue) { return; }
this._value = newValue;
this.onChange(newValue);
this.onTouched();
this.touched = true // 이벤트 트리거 시 컨트롤 표시
}
get value(): T {
return this._value;
}
writeValue(value: T): void {
this.value = value;
}
registerOnChange(fn: (value: T) => {}): void {
this.onChange = fn;
}
registerOnTouched(fn: () => {}): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
validate(control: AbstractControl<any, any>): ValidationErrors | null {
return this.required && this.touched ? !this._value ? { required: true } : null : null
}
registerOnValidatorChange?(fn: () => void): void {
this.onValidationchange = fn
}
}
1. 커스텀 컨트롤 엑세스
<input
#input
[disabled]="disabled"
[(ngModel)]="value"
[maxlength]="maxlength ?? null"
[placeholder]="placeholder ?? ''"
[autofocus]="_autofocus"
oninput="this.value = this.value.replace(/[^0-9.]/g, '')
.replace(/(\..*)\./g, '$1')
.replace(/^(\d{0,3})(\d{0,4})(\d{0,4})$/, `$1-$2-$3`)
.replace(/\-{1,2}$/g, '');"
class="w-full px-3 py-2.5 text-sm border outline-none border-gray-200 hover:border-gray-500 focus-within:border-gray-500 rounded-md"
/>
import { ReactiveFormsModule, NgControl, FormsModule } from '@angular/forms';
import {
Component,
Input,
ElementRef,
Optional,
Self,
ViewChild,
} from '@angular/core';
import { CustomValueAccessor } from '../custom-value-accessor';
@Component({
selector: 'app-input-tel',
templateUrl: './input-tel.component.html',
styleUrls: ['./input-tel.component.scss'],
standalone: true,
imports: [ReactiveFormsModule, FormsModule],
})
export class InputTelComponent extends CustomValueAccessor<string> {
_autofocus: boolean = false;
@ViewChild('input') input: ElementRef<HTMLInputElement> | undefined;
@Input() class?: string | undefined;
@Input() id?: string | undefined;
@Input() formControlName?: string | undefined;
@Input() fixText?: string | undefined;
@Input() maxlength?: string | number | null;
@Input() placeholder?: string | undefined;
@Input() override required: boolean = false;
@Input()
set autofocus(value: string | boolean) {
this._autofocus = typeof value === 'string' ? value !== undefined : value;
}
constructor(@Self() @Optional() public ngControl: NgControl) {
super();
if (this.ngControl) {
this.ngControl.valueAccessor = this;
}
}
get invalid(): boolean {
return (this.ngControl?.dirty ?? false) && !this.ngControl?.valid;
}
}
<app-input-tel
placeholder="지점 대표번호를 입력해주세요"
formControlName="representativeTel"
maxlength="13"
/>
2. 단일 input 템플릿 및 컴포넌트 (핸드폰 번호만 받기)
<app-input-tel
formControlName="tel"
placeholder="숫자로만 입력해주세요"
maxlength="13"
class="w-full rounded-md border border-gray-200"
/>
3. input 컴포넌트를 장착하는 템플릿 예시
<input
#input
[disabled]="disabled"
[type]="type"
[(ngModel)]="value"
[maxlength]="maxlength ?? null"
[placeholder]="placeholder ?? ''"
[autofocus]="_autofocus"
class="w-full px-3 py-2.5 text-sm border outline-none border-gray-200 hover:border-gray-500 focus-within:border-gray-500 rounded-md"
/>
import {
Component,
ElementRef,
Input,
Optional,
Self,
ViewChild,
} from '@angular/core';
import { FormsModule, NgControl } from '@angular/forms';
import { CustomValueAccessor } from '../custom-value-accessor';
@Component({
selector: 'app-input-text',
templateUrl: './input-text.component.html',
styleUrls: ['./input-text.component.scss'],
imports: [FormsModule],
standalone: true,
})
export class InputTextComponent extends CustomValueAccessor<string> {
_autofocus: boolean = false;
@ViewChild('input') input: ElementRef<HTMLInputElement> | undefined;
@Input() label?: string;
@Input() icon?: string;
@Input() placeholder?: string;
@Input() fixText?: string;
@Input() debounce?: number;
@Input() type?: 'text' | 'password' = 'text';
@Input() maxlength?: string | number | null;
@Input() override required: boolean = false;
@Input()
set autofocus(value: string | boolean) {
this._autofocus = typeof value === 'string' ? value !== undefined : value;
}
get invalid(): boolean {
return (this.ngControl?.dirty ?? false) && !this.ngControl?.valid;
}
constructor(@Self() @Optional() public ngControl: NgControl) {
super();
if (this.ngControl) {
this.ngControl.valueAccessor = this;
}
}
}
4. 단일 컴포넌트 text 타입 예시
import {
Component,
Input,
ElementRef,
Optional,
Self,
ViewChild,
} from '@angular/core'; // custom
import { FormsModule, NgControl, ReactiveFormsModule } from '@angular/forms'; // custom
import { CustomValueAccessor } from '../custom-value-accessor'; // custom
@Component({
selector: 'app-input-text',
templateUrl: './input-text.component.html',
styleUrls: ['./input-text.component.scss'],
standalone: true,
imports: [FormsModule, ReactiveFormsModule],
})
export class InputTextComponent extends CustomValueAccessor<string> {
// custom
// ViewChild decorator : 자식 컴포넌트, 디렉티브, rendered Dom 에 직접 접근이 가능하도록 함 -> 데이터 전송 가능
// 부모 Component template안에 위치한 모든 자식 요소들을 ViewChild라고 합니다.
// 부모 Component와 자식 Component가 데이터를 공유하는게 아니라 부모 Component가 직접 자식 Component 객체를 제어하는 방식입니다.
_autofocus: boolean = false;
// ElementRef : Template Reference Variable을 이용해서 해당 Element의 Reference를 획득,
// ElementRef type의 객체를 획득 nativeElement 속성으로 직접 제어할 수 있음!! -> rendered Dom 제어
@ViewChild('input') input: ElementRef<HTMLInputElement> | undefined;
@Input() class?: string | undefined;
@Input() id?: string | undefined;
@Input() formControlName?: string | undefined;
@Input() fixText?: string | undefined;
@Input() maxlength?: string | number | null;
@Input() placeholder?: string | undefined;
@Input() override required: boolean = false;
@Input()
set autofocus(value: string | boolean) {
this._autofocus = typeof value === 'string' ? value !== undefined : value;
}
// @Optional : 프로퍼티 데코레이터를 사용하면 의존성 객체를 찾지 못했을 때 에러를 발생하는 대신 null을 주입합니다.
constructor(@Self() @Optional() public ngControl: NgControl) {
super(); // custom
if (this.ngControl) {
// NgControl : It binds a FormControl object to a DOM element
this.ngControl.valueAccessor = this; // valueAccessor : ControlValueAccessor | null : The value accessor for the control
}
}
get invalid(): boolean {
// dirty : Reports whether the control is dirty,
// meaning that the user has changed the value in the UI. If the control is not present, null is returned.
return (this.ngControl?.dirty ?? false) && !this.ngControl?.valid;
// valid : A control is considered valid if no validation errors exist with the current value. If the control is not present, null is returned.
}
}
5. 상세 설명 버전
반응형
LIST