[Angular 마스터하기] Day 12 - Reactive Forms, 복잡한 폼 마스터하기
[Angular 마스터하기] Day 12 - Reactive Forms, 복잡한 폼 마스터하기
이제와서 시작하는 Angular 마스터하기 - Day 12 “더 강력하고 유연한 Reactive Forms를 마스터하세요! 🎯”
오늘 배울 내용
- Reactive Forms vs Template-driven Forms
- FormGroup, FormControl
- FormBuilder 사용
- 커스텀 유효성 검사
- 동적 폼
1. Reactive Forms 시작하기
ReactiveFormsModule 임포트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-reactive-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<input formControlName="email" placeholder="이메일">
<input formControlName="password" type="password" placeholder="비밀번호">
<button type="submit" [disabled]="loginForm.invalid">로그인</button>
</form>
`
})
export class ReactiveFormComponent {
loginForm = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [Validators.required, Validators.minLength(8)])
});
onSubmit() {
if (this.loginForm.valid) {
console.log(this.loginForm.value);
}
}
}
FormBuilder로 간단하게
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { Component, inject } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-form-builder-example',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<input formControlName="name" placeholder="이름">
<input formControlName="email" placeholder="이메일">
<input formControlName="age" type="number" placeholder="나이">
<button type="submit" [disabled]="userForm.invalid">제출</button>
<div class="debug">
<p>폼 값: </p>
<p>유효성: </p>
</div>
</form>
`
})
export class FormBuilderExampleComponent {
private fb = inject(FormBuilder);
userForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
email: ['', [Validators.required, Validators.email]],
age: [0, [Validators.required, Validators.min(1), Validators.max(120)]]
});
onSubmit() {
console.log(this.userForm.value);
}
}
2. 유효성 검사와 에러 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Component({
selector: 'app-validation-example',
standalone: true,
imports: [ReactiveFormsModule, CommonModule],
template: `
<form [formGroup]="form">
<div class="form-group">
<label>이메일</label>
<input formControlName="email" placeholder="example@email.com">
@if (email.invalid && email.touched) {
<div class="error">
@if (email.errors?.['required']) {
<span>이메일은 필수입니다</span>
}
@if (email.errors?.['email']) {
<span>올바른 이메일 형식이 아닙니다</span>
}
</div>
}
</div>
<div class="form-group">
<label>비밀번호</label>
<input formControlName="password" type="password">
@if (password.invalid && password.touched) {
<div class="error">
@if (password.errors?.['required']) {
<span>비밀번호는 필수입니다</span>
}
@if (password.errors?.['minlength']) {
<span>최소 {{ password.errors?.['minlength'].requiredLength }}자 필요</span>
}
</div>
}
</div>
</form>
`,
styles: [`
.error {
color: #f44336;
font-size: 0.9em;
margin-top: 5px;
}
`]
})
export class ValidationExampleComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]]
});
// FormControl에 쉽게 접근
get email() {
return this.form.get('email')!;
}
get password() {
return this.form.get('password')!;
}
}
3. 커스텀 Validator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
// 비밀번호 일치 검사
export function passwordMatchValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const password = control.get('password');
const confirmPassword = control.get('confirmPassword');
if (!password || !confirmPassword) {
return null;
}
return password.value === confirmPassword.value ? null : { passwordMismatch: true };
};
}
// 사용 예
@Component({
template: `
<form [formGroup]="registerForm">
<input formControlName="password" type="password">
<input formControlName="confirmPassword" type="password">
@if (registerForm.errors?.['passwordMismatch'] && registerForm.touched) {
<div class="error">비밀번호가 일치하지 않습니다</div>
}
</form>
`
})
export class RegisterComponent {
private fb = inject(FormBuilder);
registerForm = this.fb.group({
password: ['', [Validators.required, Validators.minLength(8)]],
confirmPassword: ['', Validators.required]
}, { validators: passwordMatchValidator() });
}
4. 동적 폼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { FormArray } from '@angular/forms';
@Component({
selector: 'app-dynamic-form',
standalone: true,
imports: [ReactiveFormsModule, CommonModule],
template: `
<form [formGroup]="form">
<h3>연락처 목록</h3>
<div formArrayName="contacts">
@for (contact of contacts.controls; track $index; let i = $index) {
<div [formGroupName]="i" class="contact-item">
<input formControlName="name" placeholder="이름">
<input formControlName="phone" placeholder="전화번호">
<button type="button" (click)="removeContact(i)">삭제</button>
</div>
}
</div>
<button type="button" (click)="addContact()">연락처 추가</button>
</form>
`
})
export class DynamicFormComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
contacts: this.fb.array([])
});
get contacts() {
return this.form.get('contacts') as FormArray;
}
addContact() {
const contactForm = this.fb.group({
name: ['', Validators.required],
phone: ['', Validators.required]
});
this.contacts.push(contactForm);
}
removeContact(index: number) {
this.contacts.removeAt(index);
}
}
📝 정리
Template-driven vs Reactive Forms
| 항목 | Template-driven | Reactive Forms |
|---|---|---|
| 설정 위치 | 템플릿 | 컴포넌트 |
| 데이터 모델 | 양방향 바인딩 | 명시적 관리 |
| 유효성 검사 | 디렉티브 | 함수 |
| 테스트 | 어려움 | 쉬움 |
| 복잡한 폼 | 어려움 | 적합 |
체크리스트
- Reactive Forms를 만들 수 있나요?
- FormBuilder를 사용할 수 있나요?
- 커스텀 Validator를 만들 수 있나요?
- 동적 폼을 만들 수 있나요?
📚 다음 학습
다음 시간에는 파이프를 배웁니다!
“Reactive Forms로 더 강력한 폼을 만드세요!” 🎯
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.