Angular Directive로 Form 조작하기

라코마코·2021년 9월 11일
2
post-thumbnail

앵귤러 개발을 하다보면 ReactiveForms 모듈을 Component에서 종종 사용해야할때가 있다.

특히 Form의 유효성 검사가 복잡해지다 보면 다른 View 영역을 제어하는 것 보다 Form을 제어하는 코드가 더 많아질때가 많다.

Html 코드

<h1>Form Test</h1>
<form [formGroup]="form">
  <label for="first">First</label>
  <input id="first" formControlName="firstName">
  <hr />
  <label for="second">Second</label>
  <input id="second" formControlName="secondName">
</form>
<ng-container *ngIf="form.errors">
  {{form.errors.message}}
</ng-container>

컴포넌트 코드

import { Component, OnInit } from '@angular/core';
import {
  AbstractControl,
  FormBuilder,
  FormGroup,
  ValidationErrors
} from '@angular/forms';

@Component({
  selector: 'app-form',
  templateUrl: './form.component.html',
  styleUrls: ['./form.component.css']
})
export class FormComponent implements OnInit {
  form = this.fb.group(
    {
      firstName: [''],
      secondName: ['']
    },
    {
      validators: [this.checkTwoName]
    }
  );
  constructor(private fb: FormBuilder) {}

  ngOnInit() {}

  private checkTwoName(nameGroup: AbstractControl): ValidationErrors | null {
    const firstName = (nameGroup as FormGroup).controls.firstName;
    const secondName = (nameGroup as FormGroup).controls.secondName;

    if (firstName.pristine || secondName.pristine) {
      return null;
    }

    if (firstName.value === secondName.value) {
      return {
        message: '첫번째 이름과 두번째 이름이 같습니다.'
      };
    }

    return null;
  }
}

사실 예제코드가 많이 부족하다. 😅 (원래는 이보다 더 많은 유효성 검사를 체크하는 함수들이 서로 엮여있다.)

부족한 부분은 Form 말고도 다른 View를 제어하는 코드가 있다고 상상으로 채워넣길 바란다.

이렇게 Form을 제어하는 코드가 많아지게 되면 이 Component는 Form을 제어하는 관심사와, View를 제어하는 2개의 관심사를 가지게 된다.

이는 Directive를 사용하면 쉽게 분리할 수 있다.

Directive를 적용한 HTML

<h1>Form Test</h1>
<form [formGroup]="form" appFormControlExample>
  <label for="first">First</label>
  <input id="first" formControlName="firstName">
  <hr />
  <label for="second">Second</label>
  <input id="second" formControlName="secondName">
</form>
<ng-container *ngIf="form.errors">
  {{form.errors.message}}
</ng-container>

Form을 제어하는 Directive

import { Directive, Input } from '@angular/core';
import { AbstractControl, FormGroup, ValidationErrors } from '@angular/forms';

@Directive({
  selector: '[appFormControlExample][formGroup]' // appFormControlExample Directive가 적용되면서 formGroup property Binding이 적용된 경우에만 붙는다.
})
export class FormControlExampleDirective {
  @Input()
  formGroup: FormGroup;

  constructor() {}

  ngOnInit() {
    this.formGroup.setValidators([this.checkTwoName]);
  }

  private checkTwoName(nameGroup: AbstractControl): ValidationErrors | null {
    const firstName = (nameGroup as FormGroup).controls.firstName;
    const secondName = (nameGroup as FormGroup).controls.secondName;

    if (firstName.pristine || secondName.pristine) {
      return null;
    }

    if (firstName.value === secondName.value) {
      return {
        message: '첫번째 이름과 두번째 이름이 같습니다.'
      };
    }

    return null;
  }
}
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';

@Component({
  selector: 'app-form',
  templateUrl: './form.component.html',
  styleUrls: ['./form.component.css']
})
export class FormComponent implements OnInit {
  form = this.fb.group({
    firstName: [''],
    secondName: ['']
  });
  constructor(private fb: FormBuilder) {}

  ngOnInit() {}
}

컴포넌트에서 깔끔하기 Form의 관심사를 분리할 수 잇다.

추가

공부하면서 알아낸건데 FormControl가 붙은 엘리먼트에 Directive를 붙이면 NgControl을 통해서 접근, 제어가 가능하다.

<form [formGroup]="form" appFormControlExample>
  <label for="first">First</label>
  <!-- appControlInput Directive를 새롭게 부착함 -->
  <input id="first" formControlName="firstName" appControlInput>
  <hr />
  <label for="second">Second</label>
  <input id="second" formControlName="secondName">
</form>
<ng-container *ngIf="form.errors">
  {{form.errors.message}}
</ng-container>
import { Directive } from '@angular/core';
import { NgControl } from '@angular/forms';

@Directive({
  selector: '[appControlInput]'
})
export class ControlInputDirective {
  // FormControl이 NgControl으로 DI되어서 접근 가능함.
  constructor(private ngControl: NgControl) {}

  ngOnInit() {
    this.ngControl.valueChanges.subscribe(data => {
      console.log(data);
    });
  }
}

FormControl만 가능하고 FormGroup은 불가능하다.

0개의 댓글