[Angular] Reactive Forms - FormControl, FormGroup, FormArray

문지은·2024년 5월 28일
0

Angular

목록 보기
3/4
post-thumbnail

Reactive Forms

  • 원하는 시점에 명시적으로 폼에 접근해서 상태를 참조하는 방식
  • 폼 상태가 변경되면 새로운 상태를 반환하기 때문에 전체 폼 모델 중 어느 부분이 변경되었는지 추적할 수 있다.
  • 반응형 폼은 옵저버블 스트림을 기반으로 동작하기 때문에, 폼에 입력되는 값도 스트림 형태로 전달된다.
  • 반응형 폼을 사용하면 개발자가 의도한 대로만 데이터가 변경되며, 현재 상태를 쉽게 예측할 수 있기 때문에 테스트하기도 편하다.
  • 데이터가 변경되는 것을 감지하는 쪽에서 값을 받아 반응하는 것도 쉽다.
  • 반응형 폼은 여러가지 면에서 템플릿 기반 폼과 다르다.
    • 반응형 폼은 데이터 모델에 동기 방식으로 접근할 수 있기 때문에 동작을 예측하기 쉬우며, 옵저버블 연산자를 활용해서 조작할 수 있고, 옵저버블 스트림을 추적하는 방식으로 변화를 감지할 수도 있다.
    • 반면에 템플릿 기반 폼은 템플릿 안에서만 동작하며 템플릿 안에 있는 디렉티브를 기반으로 동작하기 때문에 동작을 폼 모델에 직접 접근할 수 없으며 비동기 방식으로만 변화를 감지할 수 있다.

Basic Form Control

import ReactiveFormsModule

  • app.module.ts 또는 FormCotrol을 사용할 컴포넌트의 .module.tsReactiveFormsModule 추가
  • 아래 예제에서는 app.module.ts 에 추가하였다.
import {ReactiveFormsModule} from '@angular/forms';
...
@NgModule({
...
  imports: [
...
    // other imports ...
    ReactiveFormsModule,
  ],
...
})
export class AppModule {}

FormControl을 가진 컴포넌트 생성

  • 아래에서는 FormControl의 생성자를 사용하여 초기 값(여기서는 빈 문자열)을 설정

name-editor.component.ts

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-name-editor',
  templateUrl: './name-editor.component.html',
  styleUrls: ['./name-editor.component.css'],
})

export class NameEditorComponent {
  name = new FormControl('');
  ...
}

template에서 control 등록

  • ReactiveFormsModule에 포함된 FormControlDirective에서 제공하는 formControl 바인딩을 사용하여 템플릿에서 폼 컨트롤을 업데이트
<label for="name">Name: </label>
<input id="name" type="text" [formControl]="name">
  • 다음과 같은 방법으로 FormControl 값을 표시할 수도 있다.
<p>Value: {{ name.value }}</p>

form control 값 변경

  • form control의 setValue() 메서드를 사용하여 값 변경 가능
  • setValue() 는 개별 컨트롤에 대한 새 값을 설정한다. 후술 할 FormGroup에서 사용할 경우 엄격하게 폼 그룹의 구조를 준수하여야 한다.
  updateName() {
    this.name.setValue('Nancy');
  }

Grouping Form Control

동적으로 생성되는 Form Field를 관리하거나, Form의 어려 컨트롤을 논리적으로 그룹화하고 관리해야할 때 Form을 그룹화할 수 있다.

FormGroup 인스턴스 생성

  • 컴포넌트 클래스에 새로운 폼 속성을 만들고 FormGroup 인스턴스로 설정
  • FormGroup을 초기화하기 위해 이름과 FormControl 이 매핑된 객체를 생성자에 제공

src/app/profile-editor/profile-editor.component.ts

import {Component} from '@angular/core';
import {FormGroup, FormControl} from '@angular/forms';

@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css'],
})
export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
...
  });
...
}

FormGroup model, view 연결, FormData 저장

src/app/profile-editor/profile-editor.component.html

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  <label for="first-name">First Name: </label>
  <input id="first-name" type="text" formControlName="firstName">
  <label for="last-name">Last Name: </label>
  <input id="last-name" type="text" formControlName="lastName">
...
	<button type="submit" [disabled]="!profileForm.valid">Submit</button>
</form>
  onSubmit() {
	const formValue = this.profileForm.getRawValue();
    console.log(this.profileForm.formValue );
  }

Nested Form Group

  • 복잡한 폼을 구성할 때, 정보의 서로 다른 영역을 더 작은 섹션으로 관리하는 것이 더 좋다.
  • 중첩된 FormGroup 인스턴스를 사용하면 대형 FormGroup을 더 작고 관리하기 쉬운 단위로 나눌 수 있다.
  • FormGroupFormControl 인스턴스와 FormGroup인스턴스를 모두 자식으로 받을 수 있다.

src/app/profile-editor/profile-editor.component.ts

import {Component} from '@angular/core';
import {FormGroup, FormControl} from '@angular/forms';
@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css'],
})
export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
    address: new FormGroup({
      street: new FormControl(''),
      city: new FormControl(''),
      state: new FormControl(''),
      zip: new FormControl(''),
    }),
  });
...
}
  • 하위 FormGroup은 formGroupName 바인딩을 사용하여 템플릿에서 폼 그룹을 연결한다.

src/app/profile-editor/profile-editor.component.html

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
	...
  <div formGroupName="address">
    <h2>Address</h2>
    <label for="street">Street: </label>
    <input id="street" type="text" formControlName="street">
    <label for="city">City: </label>
    <input id="city" type="text" formControlName="city">
    <label for="state">State: </label>
    <input id="state" type="text" formControlName="state">
    <label for="zip">Zip Code: </label>
    <input id="zip" type="text" formControlName="zip">
  </div>
</form>
  • patchValue() 메서드를 사용하면 폼 컨트롤 데이터 모델의 특정 부분만 업데이트 가능하다.

src/app/profile-editor/profile-editor.component.ts

updateProfile() {
  this.profileForm.patchValue({
    firstName: 'Nancy',
    address: {
      street: '123 Drew Street',
    },
  });
}

Dynamic Forms - Form Array

FormArray클래스 import 및 FormArray Control 정의

src/app/profile-editor/profile-editor.component.ts

import {Component} from '@angular/core';
import {FormArray, FormGroup, FormControl} from '@angular/forms';
@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css'],
})
export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
    address: new FormGroup({
      street: new FormControl(''),
      city: new FormControl(''),
      state: new FormControl(''),
      zip: new FormControl(''),
    }),
    histories: new FormArray([]),
  });
...
}

array 데이터 추가/삭제 함수 작성

get histories() {
  return this.profileForm.get('histories') as FormArray;
}
 
add() {
		this.histories.push(
      new FormGroup({
        name: new FormControl('', { validators: [Validators.required] }),
        description: new FormControl('', { validators: [Validators.required] }),
      })
    );
  }

remove(index: number) {
  this.histories.removeAt(index);
}

템플릿에 form control 바인딩

  • formArrayName바인딩을 사용하여 템플릿에서 FormArray 연결
  • 반복문을 실행하며 formGroup바인딩을 사용하여 FormArrayFormGroup 연결
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
	...
	<div formArrayName="histories">
    @for (control of form.controls.histories.controls; track idx ; let idx = $index) {
    <form [formGroup]="control">
	    <label for="name">Name: </label>
      <input id="name" type="text" formControlName="name">
      <label for="description">Description: </label>
      <input id="description" type="text" formControlName="description">
      <button (click)="add()">추가</button>
      <button (click)="remove(idx)">삭제</button>
    </form>
  </div>
</form>
  • 수정 페이지와 같이 데이터가 있는 경우 페칭하는 상황에서, Form Array를 페칭할 때는 array 데이터를 하나씩 넣어주어야 한다.
// form 페칭
setForm(data) {
  this.form.patchValue({
    ...data,
    histories: [],
  });
  data.histories.map((history) => {
    const histories = this.form.get('histories') as FormArray;
    histories.push(
      new FormGroup({
        title: new FormControl(history.title, [Validators.required]),
        active: new FormControl(history.active, [Validators.required]),
      })
    );
  });
}

References

Angular 공식문서

profile
코드로 꿈을 펼치는 개발자의 이야기, 노력과 열정이 가득한 곳 🌈

0개의 댓글