티스토리 뷰

728x90

1. 하나의 form control을 검증하는 게 아닌 form 전체에 대한 검증에 대한 포스트이다.

  1-1 Java의 경우에도 Annoation을 생성하여 Form Control 상호 검증이 가능한데 유사한 개념이다.

  1-2 javascript(typescript)의 경우는 functional programming 태생이라 간단히 함수만 추가로 구현하면 된다.

  1-3 typescript는 interface를 사용하면 간결하기 때문에 많이 사용하는데, 웃긴건 너무 많이 너무 타입이 많다.

    1-3-1 그래서 타입 정의 찾아다닌다고 짜증나는 경우가 생긴다.

 

2. 프로젝트를 하나 생성하고 검증을 구현할 컴포넌트 하나를 생성한다.

 

 

  2-1 이제 template을 작성한다.

    2-1-1 passwordconfirm.component.html이다.

 

<div class="container">
  <form [formGroup]="passwordFormGroup">
    <div class="form-group">
      <label for="password" class="form-label">Password</label>
      <input type="text" class="form-control" id="password" formControlName="password">
    </div>
    <div class="form-group">
      <label for="passwordConfirmation" class="form-label">Password Confirmation</label>
      <input type="text" class="form-control" id="passwordConfirmation" formControlName="passwordConfirmation">
    </div>
  </form>
</div>

<hr>

<div>Form is valid: {{ passwordFormGroup.valid }}</div>
<div>Form values: {{ passwordFormGroup.value | json }}</div>
<div>Form errors: {{ passwordFormGroup.errors | json }}</div>

 

  2-1-2 이 컴포넌트를 사용하는 app 컴포넌트 template을 작성한다.

 

<div>Form is valid: {{ addition.valid }}</div>
<div>Form values: {{ addition.value | json }}</div>
<div>Form errors: {{ addition.errors | json }}</div>

<hr>

<app-passwordconfirm></app-passwordconfirm>

 

 

3. custom 검증함수 추가하기

  3-1 ReactiveFormsModule은 2가지의 form group 생성방식을 제공한다.

    3-1-1 첫번째는 FormGroup, FormControl을 사용하는 방법이고

    3-1-2 두번째는 FormBuilder를 사용하는 방법이다.

  3-2 첫번째 방식

    3-2-1 아래처럼 기본 껍데기를 구현한다. 그리고 fornGroup의 생성자의 두번째 인자를 추가한다.

 

export class PasswordconfirmComponent implements OnInit {
  passwordFormGroup = new FormGroup({
    password: new FormControl('', [
      Validators.required,
      Validators.minLength(3),
      Validators.maxLength(20),
    ]),
    passwordConfirmation: new FormControl('', [
      Validators.required,
      Validators.minLength(3),
      Validators.maxLength(20),
    ]),
  }, );

 

    3-2-2 첫번째 인자 객체뒤에 , 를 추가하면 화면이 뜬다. 어디에 검증로직을 추가하는지가 제일 중요하다.

      3-2-2-1 내용은 ValidatorFn | ValidatorFn[] | AbstractControlOptions 타입의 함수를 제공하라는 의미다.

      3-2-2-2 이 예제의 경우 하나의 함수만 있으면 되니 굳이 []를 사용하지 않고 그냥 함수를 넣으면 된다.

      3-2-3-3 한가지 더 말하고 싶은 부분은 세번째 인자인데 것은 비동기식 검증함수이다.

        3-2-3-3-1 즉 서버와 통신 후에 검증 결과를 Observable을 통해 전달하는 방식이다.

 

 

    3-2-3 간단한 비밀번호 검증함수를 추가한다.

      3-2-3-0 AbstractControl은 FormControl, FormGroup의 추상클래스이다.

      3-2-3-1 단순히 두 FormControl의 값을 비교하여 다르면 notMatched 속성을 추가하고 같으면 null을 반환한다.

      3-2-3-2 반환하는 ValidationErrors는 단순한 객체정의로 key만 문자열이면 되는 단순한 객체이다.

 

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

@Component({
  selector: 'app-passwordconfirm',
  templateUrl: './passwordconfirm.component.html',
  styleUrls: ['./passwordconfirm.component.css'],
})
export class PasswordconfirmComponent implements OnInit {
  passwordFormGroup = new FormGroup(
    {
      password: new FormControl('', [
        Validators.required,
        Validators.minLength(3),
        Validators.maxLength(20),
      ]),
      passwordConfirmation: new FormControl('', [
        Validators.required,
        Validators.minLength(3),
        Validators.maxLength(20),
      ]),
    },
    (form: AbstractControl): ValidationErrors  => {
      const { password, passwordConfirmation } = form.value;
      if (password !== passwordConfirmation) {
        return { notMatched: true };
      } else {
        return null;
      }
    }
  );

  ngOnInit(): void {}
}

 

    3-2-4 결과화면

 

  3-3 두번째 Form Builder 사용하는 경우

    3-3-1 아래처럼 동일하게 기본 코드를 작성한다.

 

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

@Component({
  selector: 'app-passwordconfirm',
  templateUrl: './passwordconfirm.component.html',
  styleUrls: ['./passwordconfirm.component.css'],
})
export class PasswordconfirmComponent implements OnInit {

  ngOnInit(): void {}

  passwordFormGroup: FormGroup;

  constructor(private formBuilder: FormBuilder) {
    this.passwordFormGroup = this.formBuilder.group(
      {
        password: [
          '',
          [
            Validators.required,
            Validators.minLength(3),
            Validators.maxLength(20),
          ],
        ],
        passwordConfirmation: [
          '',
          [
            Validators.required,
            Validators.minLength(3),
            Validators.maxLength(20),
          ],
        ],
      }, );
  }
}

 

    3-3-2 첫번째 방식과 동일하게 첫번째 인자 뒤에서 , 를 입력하면 vscode에서는 도움창이 뜬다.

      3-3-2-1 첫번째 방식과는 다르게 인자가 3개가 아니고 2개이다.

        3-3-2-1-1 하나의 인자객체로 동기, 비동기를 다 처리하겠다는 뜻이다.

          3-3-2-1-1-1 그 인자는 AbstractContolOptions이나 일반적인 객체를 받는다고 한다.

 

        3-3-2-1-2 설명을 좀 더 보면 두 가지의 경우 어떤 속성을 제공해야 하는지 명시하고 있다.

          3-3-2-1-2-1 두 경우 모두 validators, asyncValidator 속성이 있어 위에서 말한 첫번째 방식 인자와 동일하다.

          3-3-2-1-2-2 사실 첫번째 방식과 동일한데 인자를 받는 방식만 다를 뿐이다.

 

    3-3-3 이제 간단한 검증로직을 아래처럼 구현한다. 동기화 검증로직이니 validator 속성을 사용하면 된다.

      3-3-3-1 아래의 경우는 validator 속성에 배열을 사용했지만, 사실 하나밖에 없으니 []을 제거해도 동일하다..

 

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

@Component({
  selector: 'app-passwordconfirm',
  templateUrl: './passwordconfirm.component.html',
  styleUrls: ['./passwordconfirm.component.css'],
})
export class PasswordconfirmComponent implements OnInit {
  ngOnInit(): void {}

  passwordFormGroup: FormGroup;

  constructor(private formBuilder: FormBuilder) {
    this.passwordFormGroup = this.formBuilder.group(
      {
        password: [
          '',
          [
            Validators.required,
            Validators.minLength(3),
            Validators.maxLength(20),
          ],
        ],
        passwordConfirmation: [
          '',
          [
            Validators.required,
            Validators.minLength(3),
            Validators.maxLength(20),
          ],
        ],
      },
      {
        validators: [
          (form: AbstractControl) => {
            const { password, passwordConfirmation } = form.value;
            if (password !== passwordConfirmation) {
              return { notMatched: true };
            } else {
              return null;
            }
          },
        ],
      }
    );
  }
}

 

    3-3-4 결과화면 - 첫번째 방식과 결과는 동일하다.

 

  3-4 어떤 방식으로 구현해도 상관없다. 어차피 결과는 동일하다.

    3-4-1 다만 builder의 경우 생성할 때 좀 더 신경쓸게 적다는 장점이 있어 내가 선호할 분이다.

728x90
댓글