반응형폼 연습문제

lee jae hwan·2022년 9월 3일

앵귤러

목록 보기
18/83

https://www.learmoreseekmore.com/2022/06/angular14-reactive-forms-example.html


반응형폼을 만들기위해서는 form이 있는 컴포넌트를 포함하는 모듈에 반응형폼 라이브러리를 임포트하고 인스턴스를 만들어야 한다.

form이 있는 컴포넌트 클래스에는 FormControl, FormGroup 라이브러리를 임포트하고 폼구조에 맞게 인스턴스를 생성해야 한다.

템플릿에는 < form >태그에 디렉티브와 클래스 인스턴스바인딩한다.

<form [formGroup]="jobForm" (ngSubmit)="save()">

디렉티브는 대소문자구분하며 formGroup에 FormGroup인스턴스를 바인딩한다.

formGroup디렉티브는 HTML표준 submit이벤트가 발생하면 가로채 ngSubmit이벤트를 발생시킨다.

  jobForm = new FormGroup({
    firstName : new FormControl(''),
    lastName :new FormControl('')
  });

템플릿내 폼컨트롤들 바인딩을 위한 인스턴스를 만들어야 한다.



위와같이 폼컨트롤들 인스턴스생성을 간략하해주는 FormBuilder가 있다.

  jobForm = this.fb.group({
    firstName:[''],
    lastName:['']
  });
  
  constructor(private fb:FormBuilder){}

FormBuilder를 의존성주입하고 배열형식으로 사용하면 간단해지며 유효성검사도 편리해진다.


모듈화를 위해 중첩그룹을 사용하는것은 일반적이므로 FormBuilder를 사용해 중첩그룹을 작성해보자

  fg = this.fb.group({
    first:[''],
    last:[''],
    contacts:this.fb.group({
      contactType:['-1'],
      email:[''],
      phone:['']
    })
  });

FormBuilder의 group메소드를 사용해 그룹내에 그룹을 정의하면 된다.

템플릿에 위 구조처럼 요소노드를 만들어야 한다.

 <form [formGroup]="fg" (ngSubmit)="save()">
 
 </form>

form태그가 첫번째 그룹이 되며 formGroup디렉티브에 FormBuilder의 fg인스턴스를 바인딩한다.

이제부터 fg인스턴스로 하위 폼컨트롤들을 접근할 수 있다.

 <form [formGroup]="fg" (ngSubmit)="save()">
   <div>firstName : <input type="text" formControlName="first" ></div>   
   <div>lastName : <input type="text" formControlName="last" ></div>

   <div formGroupName="contacts">
      <select formControlName="contactType">
         <option value="-1">select</option>
         <option value="email">email</option>
         <option value="phone">phone</option>
      </select>
      <div>email : <input type="text" formControlName="email" ></div>
      <div>phone : <input type="text" formControlName="phone" ></div>
   </div> 
 </form>

[formGroup]이 바인딩됬기 때문에 하위 폼컨트롤들은 바인딩할 필요가 없다.


FormControlName 디렉티브

<form [formGroup]="fg" >
<input type="text" formControlName="phone" >
</form>

FormGroup내부에 있는 폼요소노드를 FormControl인스턴스 이름으로 동기화시킨다.

이것은 바인딩하는 것이 아니고 formGroup하위에 있는 폼요소노드에 접근할 수 있는 통로를 제공한다.

this.fg.controls.first; // firstName 요소노드에 접근이 가능하다.
this.fg.get('first');

this.fg.controls.contacts.controls.contactType;
this.fg.get('contacts.contactType');

클래스코드에서 인스턴스 이름으로 폼요소노드에 접근이 가능하다.


폼컨트롤 동적생성 매커니즘

  1. 폼빌더 array메소드로 FormArray타입의 인스턴스를 만든다.
  fg = this.fb.group({
    first:['',Validators.required],
    last:[''],
    contacts:this.fb.group({
      contactType:['-1',emailOrPhoneRequired()],
      email:[''],
      phone:['']
    }),
    skills:this.fb.array([]) as FormArray,// 빈배열 인스턴스생성, FormArray타입으로 지정하지 않으면 추상폼컨트롤타입으로 된다.
  });

2. skills배열에 push메소드를 사용해서 폼컨트롤 그룹 또는 인스턴스를 추가한다.
  addSkill(){
    this.fg.controls.skills.push(
      this.fb.group({
        programLanguage:['',Validators.required],
        experience:['']
      })
    );
  }

3. 템플릿에서 *ngFor디렉티브로 skills내 컨트롤들을 반복구성한다.
<ng-container *ngFor="let skill of fg.controls.skills.controls; let i=index">
         <div [formGroupName]="i">
            <div>programLanguage : <input type="text" formControlName="programLanguage"></div>
            <div>experience : <input type="text" formControlName="experience"></div>         
            <div><button type="button" (click)="deleteSkill(i)">Delete skill</button></div>
         </div>
</ng-container>      

주의사항
1. *ngFor디렉티브로 반복순회할때 fg.controls.skills.controls로해야 이터레이터가 적용된다.

fg.controls.skills로 사용해도 되지만 조금 간략화하려면 getter함수를 만들어 사용하면 된다.

  get skillsForm(){
    return this.fg.get('skills') as FormArray;
  }

fg.controls.skills.controls를 skillsForm.controls로 줄일 수 있다.

get함수를 사용하면 타입이 추상폼컨트롤타입이 되므로 타입단언을 사용해야 한다.


  1. ngFor 바인딩
      <ng-container *ngFor="let skill of fg.controls.skills.controls; let i=index">
         <div [formGroupName]="i">
            <div>programLanguage : <input type="text" formControlName="programLanguage"></div>
            <div>experience : <input type="text" formControlName="experience"></div>         
            <div><button type="button" (click)="deleteSkill(i)">Delete skill</button></div>
         </div>
      </ng-container>      

ngFor 디렉티브는 fg.controls.skills.controls 갯수만큼 반복순회를 한다.

< div formGroupName="i" > 와 < div [formGroupName]="i" > 차이를 이해해야 한다.

< div formGroupName="i" >은 바인딩없이 formGroupName의 동기화를 하는데 i는 블럭범위변수로 반복시마다 증가하기때문에 바인딩을 해야 참조연결이 된다.



폼컨트롤의 유효성검사

  fg = this.fb.group({
    first:['',Validators.required],
    last:[''],
    contacts:this.fb.group({
      contactType:['-1',emailOrPhoneRequired()],
      email:[''],
      phone:['']
    }),
    skills:this.fb.array([]) as FormArray,
  });

폼빌더 배열의 두번째 인자에 유효성검사함수를 전달하면 인스턴스는 valid, invalid프로퍼티가 활성화되며 유효성 결과를 boolean값을 가지게 된다.

Validators.required는 앵귤러의 내장 validation메소드다.

emailOrPhoneRequired()는 커스텀 유효성검사 디렉티브이며 유효성검사함수를 반환한다.

export function emailOrPhoneRequired():ValidatorFn{

  return (control:AbstractControl):ValidationErrors | null=>{

    return control.value == '-1'? {emailOrPhoneRequired:control.value}:null;
  }

}


동적생성 폼컨트롤의 유효성검사

동적생성된 입력컨트롤의 유효성검사를 해야하려면 폼컨트롤그룹에 접근하는 방법이 있어야 한다.

  getProgramingLagnuage(i:number){
    return this.fg.controls.skills.at(i).get('programLanguage');
  }

formControlName="programLanguage" 반복되는 그룹마다 같은 이름으로 디렉티브가 동기화되지만 독립적인 그룹으로 구분되기때문에 개별그룹으로 접근후 폼컨트롤에 접근할 수 있다.

<div [formGroupName]="i">
     <div>programLanguage : <input type="text" formControlName="programLanguage"></div>
     <div *ngIf="getProgramingLagnuage(i)?.invalid && (getProgramingLagnuage(i)?.dirty || getProgramingLagnuage(i)?.touched) " >.......</div>
     <div>experience : <input type="text" formControlName="experience"></div>         
      <div><button type="button" (click)="deleteSkill(i)">Delete skill</button></div>
</div>

*ngIf로 그룹내 폼컨트롤에 접근하여 유효성검사를 할 수 있다.

0개의 댓글