[Angular]Reactive Form 정리
Reactive Form 이란
반응형 폼(Reactive Form)이란 Form에서 받아오는 데이터의 변화를 감지하여 항상 새로운 상태의 데이터를 표시할 수 있게 만들어주는 방법이다.
Reactive Form API
클래스
클래스 | 설명 |
---|---|
AbstractControl | 폼 컨트롤을 표현하는 FormControl, FormGroup, FormArray의 추상 클래스로 폼 컨트롤의 공통 기능과 프로퍼티를 정의한다. |
FormControl | 개별 폼 컨트롤의 값과 유효성 검사 상태를 관리하는 클래스로 HTML 문서의 <input> 혹은 <select> 엘리먼트와 연동된다. |
FormGroup | 연관된 AbstractControl 인스턴스를 그룹으로 관리할 때 사용하는 클래스로 자식 폼 컨트롤을 프로퍼티로 관리하며, 그룹에 접근해도 자식 폼 컨트롤의 값이나 유효성 검사 상태도 확인할 수 있다. |
FormArray | AbstractControl을 배열 형태로 관리할 때 사용하는 클래스다. |
FormBuilder | 폼 컨트롤 인스턴스를 간편하게 만들때 사용하는 서비스다 |
디렉티브
디렉티브 | 설명 |
---|---|
FormControlDirective | 개별 FormControl 인스턴스와 폼 컨트롤 엘리먼트를 연결 |
FormControlName | 이름으로 기준으로 FormGroup 안에 있는 FormControl을 연결 |
FormGroupDirective | FormGroup 인스턴스를 DOM 엘리먼트와 연결 |
FormGroupName | 중첩된 FormGroup 인스턴스를 DOM 엘리먼트와 연결 |
FormArrayName | FormArray 인스턴스를 DOM 엘리먼트와 연결 |
FormControl
개별 폼 컨트롤의 값과 유효성 검사 상태를 관리하는 클래스로 HTML 문서의 <input>
혹은 <select>
엘리먼트와 연동된다.
FormControl 추가하기
- 앱에 반응형 폼 모듈을 로드
import { ReactiveFormsModule } from '@angular/forms'; @NgModule({ imports: [ // 다른 모듈들... ReactiveFormsModule ], }) export class AppModule { }
- 컴포넌트에 FormControl 인스턴스를 정의
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(''); }
- 템플릿에 FormControl을 등록
<label for="name">Name: </label> <input id="name" type="text" [formControl]="name">
- 컴포넌트 표시
<app-name-editor></app-name-editor>
FormControl 표시하기
<p>Value: {{ name.value }}</p>
폼 컨트롤 엘리먼트의 값이 변경될 때마다 자동으로 갱신된다.
FormControl 변경하기
setValue()
반응형 폼에서 제공하는 메소드 setValue()
메소드를 사용하면 폼 컨트롤의 값을 변경할 수 있으며, 유효성 검사도 함께 수행 해주기 때문에 현재 폼 컨트롤의 값 전체를 한 번에 변경할 수 있다.
updateName() { this.name.setValue('Nancy'); }
<button (click)="updateName()">Update Name</button>
이 때 폼 컨트롤의 값은 폼 모델과 연결되어 있기 때문에, 버튼을 눌러서 폼 컨트롤의 값이 변경되면 컴포넌트 클래스에 있는 폼 모델 값도 함께 변경된다.
FormGroup
연관된 AbstractControl 인스턴스를 그룹으로 관리할 때 사용하는 클래스로 자식 폼 컨트롤을 프로퍼티로 관리하며, 그룹에 접근해도 자식 폼 컨트롤의 값이나 유효성 검사 상태도 확인할 수 있다.
FormControl과 마찬가지로 value
나 untouched
프로퍼티, setValue()
메소드를 제공한다.
Single FormGroup
- @angular/forms 패키지에서 재공하는 심볼 로드
import { FormGroup, FormControl } from '@angular/forms';
- FormGroup 인스턴스를 추가
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 모델과 화면을 연결
<form [formGroup]="profileForm"> <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"> </form>
- 폼 데이터를 저장합니다.
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button>
onSubmit() { // TODO: EventEmitter로 폼 내용 보내기 console.warn(this.profileForm.value); }
<app-profile-editor></app-profile-editor>
Nesting Form Group
FormGroup은 FormGroup안에 있는 개별 FormControl 인스턴스에 직접 접근할 수 있어 폼 그룹을 논리적으로 구성하면 폼 전체를 한 번에 관리하기 편해진다.
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('') }) }); }
<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>
patchValue()
setValue()
메소드를 사용하면 인자의 형태가 복잡한 폼 구조에 맞을 때만 값을 변경할 수 있지만 patchValue()
메소드는 폼 그룹의 구조와 다른 인자가 전달되어도 에러를 발생하지 않는다.
updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street' } }); }
<button (click)="updateProfile()">Update Profile</button>
FormBuilder
FormBuilder는 직접 생성하지 않고 FormControl 인스턴스를 간편하게 만들때 사용하는 서비스다.
- FormBuilder 클래스를 로드
import { FormBuilder } from '@angular/forms';
- FormBuilder 서비스를 의존성으로 주입
constructor(private fb: FormBuilder) { }
- FormBuilder 서비스로 폼을 구성
import { Component } from '@angular/core'; import { FormBuilder } from '@angular/forms'; @Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'] }) export class ProfileEditorComponent { profileForm = this.fb.group({ firstName: [''], lastName: [''], address: this.fb.group({ street: [''], city: [''], state: [''], zip: [''] }), }); constructor(private fb: FormBuilder) { } }
FormControl을 직접 생성하는 것과 FormBuild로 생성하는 것의 차이
profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl('') }) });
profileForm = this.fb.group({ firstName: [''], lastName: [''], address: this.fb.group({ street: [''], city: [''], state: [''], zip: [''] }), });
FormArray
AbstractControl을 배열 형태로 관리할 때 사용하는 클래스로 폼 컨트롤에 이름이 없고 폼 컨트롤 개수가 변한다면 FormGroup 대신 FormArray를 사용하면 좋다. FormGroup 인스턴스와 마찬가지로 FormArray도 폼 컨트롤을 동적으로 추가하거나 제거할 수 있으며, 자식 FormGroup을 모아 값이나 유효성 검사 결과를 한 번에 참조할 수도 있다. FormArray는 이름을 지정하는 방식으로 정의하지 않는데, 자식 폼 컨트롤의 개수가 몇개인지 몰라도 된다.
- FormArray 클래스를 로드
import { FormArray } from '@angular/forms';
- FormArray 컨트롤을 정의
profileForm = this.fb.group({ firstName: ['', Validators.required], lastName: [''], address: this.fb.group({ street: [''], city: [''], state: [''], zip: [''] }), aliases: this.fb.array([ this.fb.control('') ]) });
- get() 메서드를 사용해서 FormArray 컨트롤에 접근
get aliases() { return this.profileForm.get('aliases') as FormArray; } addAlias() { this.aliases.push(this.fb.control('')); }
- 템플릿에 FormArray를 연결
<div formArrayName="aliases"> <h2>Aliases</h2> <button (click)="addAlias()" type="button">+ Add another alias</button> <div *ngFor="let alias of aliases.controls; let i=index"> <!-- alias 폼 배열이 반복되는 부분 --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i"> </div> </div>
Form Validation
폼 유효성 검사(Form validation) 는 사용자가 필수 항목을 모두 입력했는지, 입력한 내용이 올바른지 검증하는 것이다.
- 폼 컴포넌트에 유효성 검사 함수를 로드
import { Validators } from '@angular/forms';
- 원하는 폼 필드에 유효성 검사기를 적용
profileForm = this.fb.group({ firstName: ['', Validators.required], lastName: [''], address: this.fb.group({ street: [''], city: [''], state: [''], zip: [''] }), });
- 폼 유효성 검사 결과에 따라 적절한 처리 로직을 추가
Form 상태 표시하는 방법
<p>Form Status: {{ profileForm.status }}</p>
validation 종류
min | 컨트롤의 값이 제공된 숫자보다 크거나 같은지 유효성 검사 |
---|---|
max | 컨트롤의 값이 제공된 숫자보다 작거나 같은지 유효성 검사 |
required | 값이 비어있지 않은지 유효성 검사 |
requiredTrue | 값이 참인지 유효성 검사(필수 체크박스) |
이메일 유효성 검사 | |
minLength | 컨트롤의 값이 제시한 최소 길이보다 크거나 같은지 유효성 검사 |
maxLength | 컨트롤의 값이 제시한 최대 길이보다 작거나 같은지 유효성 검사 |
pattern | 정규식 패턴과 일치하는지 유효성 검사 |
nullValidator | 작업을 수행하지 않는 유효성 검사 |
compose | 제공된 컨트롤에 대한 개별 오류 맵의 합집합을 반환하는 단일 함수로 여러 유효성 검사 |
composeAsync | 여러 비동기 유효성 검사기를 제공된 컨트롤에 대한 개별 오류 개체의 합집합을 반환 |
class Validators { static min(min: number): ValidatorFn static max(max: number): ValidatorFn static required(control: AbstractControl): ValidationErrors | null static requiredTrue(control: AbstractControl): ValidationErrors | null static email(control: AbstractControl): ValidationErrors | null static minLength(minLength: number): ValidatorFn static maxLength(maxLength: number): ValidatorFn static pattern(pattern: string | RegExp): ValidatorFn static nullValidator(control: AbstractControl): ValidationErrors | null static compose(validators: ValidatorFn[]): ValidatorFn | null static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn | null }