Angular Form
2019년에 Hexo 블로그에 쓴 글을 옮겨 왔습니다. 당시 이해를 그대로 두었습니다.
Angular Form
Angular Form은 Template-driven Forms와 Ractive Forms를 제공한다.
Template-driven Forms
- 간단한 form (Form Control 한두개)을 구성할 때 사용
Reactive Forms
- 복잡한 form을 구성할 때 사용
Form은 Client side에서 중요한 역할을 한다. 사용자의 입력을 서버로 전송하고 페이지를 전환할 수 있는 element이다. Server는 많은 Client 요청을 처리하므로 form을 통해서 받은 사용자 입력의 유효성 검사(validation)를 Client side에서 하는 것이 기본이다.
Template-driven Forms
-
Template-driven Form은 Component template에서 Directive를 사용해서 Form을 구성하는 방식
-
각 field의 형식이나 validation 검증 규칙을 모두 template에서 정의
-
Template-driven Form은 NgForm, NgModel, NgModelGroup Directive 중심으로 동작
// app.module.ts import { FormsModule } from '@angular/forms'; @NgModule({ imports: [FormsModule], })
NgForm Directive
-
Template-driven Form 전체를 관리하는 Directive
-
root module에 FormsModule 추가하면 NgForm Directive 선언하지 않아도 모든 form element에 자동으로 적용되어 Template-driven Form으로 동작
-
HTML standard form의 validation을 사용하지 않고 Template-driven Form의 validation을 사용하려면 아래와 같이 form element에
novalidateAttribute를 추가한다. 최신 Angular는 FormsModule을 import하면novalidate가 자동으로 추가된다.<form novalidate></form> -
반대로 NgForm Directive를 사용하지 않고 HTML standard form을 사용하려면
ngNoForm을 추가한다.<form ngNoForm></form>
HTML Standard Form과 Template-driven Form의 차이
HTML Standard Form은 submit button 클릭시 form data를 서버로 전송하고 페이지를 전환
NgForm Directive가 적용된 Template-driven Form은 submit event를 Intercept해서 submit의 기본 동작을 막음
- 대신 submit시 ngSubmit event를 사용해서 onSubmit event handler 사용
<form #f="ngForm" (ngSubmit)="onNgSubmit(f)" novalidate> </form>
#f="ngForm"으로 Native DOM 대신 NgForm Instance를 Template 참조 변수에 할당한다.- Native DOM 요소와 다르게 ngForm Instance는 ng-valid, ng-touched 등 유용한 프로퍼티를 제공한다.
- NgForm Directive는 NgForm Instance 생성시 자신이 적용된 Form 요소의 값이나 validation 검증 산태를 추적할 수 있는 FormGroup Instance를 생성한 후 NgForm Instance의 property로 할당한다.
<form #f="ngForm" (ngSubmit)="onSubmit(f)">
<input
type="text"
placeholder="email"
name="email"
pattern="^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$"
ngModel
required
#email="ngModel"
/>
<input
type="password"
placeholder="password"
name="password"
pattern="^[a-zA-Z0-9]{4,10}$"
ngModel
required
#password="ngModel"
/>
<button [disabled]="f.invalid">Submit</button>
</form>
Form의 Input 요소들에 ngModel을 추가해야 Form의 Form Control로 등록된다.
Reactive Forms
- Template이 아닌 Component class에서 form element의 상태를 관리하는 객체인 Form Model을 구성하는 방식
- Template-driven Form 보다 복잡한 form을 구성할 때 사용
// app.module.ts
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [ReactiveFormsModule],
})
-
Reactive Form은 FormGroup, FormControl, FormArray를 중심으로 동작
import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl } from '@angular/forms'; @Component({ selector: 'app-root', template: ` <form [formGroup]="userForm"> <input type="text" placeholder="userid" formControlName="userid" /> <input type="password" placeholder="userpw" formControlName="userpw" /> <button>Login</button> </form> `, styles: [] }) export class AppComponent implements OnInit { userForm: FormGroup; constructor() {} ngOnInit() { this.userForm = new FormGroup({ userid: new FormControl(), userpw: new FormControl() }); } }-
FormGroup Instance가 template의 form을 관리하도록 mapping
<form [formGroup]="userForm"></form>
-
-
FormGroup의 FormControl을 생성해서 input element와 mapping
<input type="text" placeholder="userid" formControlName="userid" /> <input type="password" placeholder="userpw" formControlName="userpw" /> -
FormControl

-
FormControl 이름으로 getter 만들기
import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl, Validators } from '@angular/forms'; @Component({ selector: 'app-root', template: ` <form [formGroup]="userForm"> <input type="text" placeholder="userid" formControlName="userid" /> <em *ngIf="userid.errors?.required && userid.touched">아이디를 입력해주세요</em> <em *ngIf="userid.errors?.pattern && userid.touched">아이디를 형식에 맞게 입력해주세요</em> <input type="password" placeholder="userpw" formControlName="userpw" /> <button>Login</button> </form> <pre>userForm.value: {{ userForm.value | json }}</pre> <pre>userForm.valid: {{ userForm.valid }}</pre> `, styles: [] }) export class AppComponent implements OnInit { userForm: FormGroup; constructor() {} ngOnInit() { this.userForm = new FormGroup({ userid: new FormControl('', [Validators.required, Validators.pattern('[a-zA-Z]{4,10}')]), userpw: new FormControl('', [Validators.required, Validators.pattern('[a-zA-Z]{4,10}')]) }); console.dir(this.userForm); } get userid() { return this.userForm.get('userid'); } } -
비밀번호 확인을 위한 Custom Validation method
<--! template --> <div formGroupName="passwordGroup"> <input type="password" placeholder="password" formControlName="password" /> <input type="password" placeholder="confirm password" formControlName="confirmPassword" /> </div>
// component.ts
ngOnInit() {
this.userForm = new FormGroup({
userid: new FormControl('', [Validators.required, Validators.pattern('[a-zA-Z]{4,10}')]),
passwordGroup: new FormGroup(
{
password: new FormControl('', [
Validators.required,
Validators.pattern('[a-zA-Z]{4,10}')
]),
confirmPassword: new FormControl('', [
Validators.required,
Validators.pattern('[a-zA-Z]{4,10}')
])
},
PasswordValidator.match
)
});
}
// password-validator.ts
import { AbstractControl } from '@angular/forms';
export class PasswordValidator {
static match(passwordGroup: AbstractControl) {
const password = passwordGroup.get('password').value;
const confirmPassword = passwordGroup.get('confirmPassword').value;
return password === confirmPassword ? null : { match: { password, confirmPassword } };
}
}
-
AbstractControl모든 Control의 조상// new FormGroup 대신 FormBuilder 사용 시 ngOnInit() { this.userForm = this.fb.group({ userid: ['', [Validators.required, Validators.pattern('[a-zA-Z]{4,10}')]], passwordGroup: this.fb.group( { password: ['', [Validators.required, Validators.pattern('[a-zA-Z]{4,10}')]], comfirmPassword: ['', [Validators.required, Validators.pattern('[a-zA-Z]{4,10}')]] }, { validator: PasswordValidator.match } ) }); }