[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
}