깜놀하는 해므찌로

ControlValueAccessor Angular 사용 예시 / CustomValueAccessor 본문

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