angular2 form
기존 angularjs에서 문제가 되었던 form
이 크게 향상되었습니다. 기존 form의 문제점은 다음과 같습니다.
- form의 생성시기를 알 수 없습니다.
div
tag를 통해서 생성되는ng-form
은 생성시기를 알 수 없기 때문에,form
을 검사하기 위해서는null
error를 꼭 check하는 것이 좋습니다. - form error text가 html에 담긴다. form에서 발생되는 error를 표시하는 방법이 모두 html에 기술되어야지 됩니다. 이에 대한 구현의 문제는
html
이 과도하게 길어지는 문제가 발생하게 되고,ng-if
의 남발이 발생하게 됩니다.
angular2
는 NgForm
의 제어 영역을 component
로 이동시켜서 기존의 html
에서 제어되는 것이 아닌 component
에서 제어되는 것으로 구현을 변경하였습니다. 물론, 기존과 같은 패턴 역시 사용가능합니다.
Form Validation - Old Pattern
기존 ngForm
을 이용하는 방법과 거의 유사합니다.
<form #heroForm="ngForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" name="name" [(ngModel)]="hero.name" #name="ngModel" class="form-control" required minlength="4" maxlength="24">
<div *ngIf="name.errors && (name.dirty || name.touched)" class="alert alert-danger">
<div [hidden]="!name.errors.required">
Name is required
</div>
<div [hidden]="!name.errors.minlength">
Name must be at least 4 characters long.
</div>
<div [hidden]="!name.errors.maxlength">
Name cannot be more than 24 characters long.
</div>
</div>
</div>
export class HeroFormTemplate1Component {
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What');
submitted = false;
active = true;
onSubmit() {
this.submitted = true;
}
addHero() {
this.hero = new Hero(42, '', '');
}
}
각 form
의 이름을 이용하고 [(ngModel)]
을 이용해서 model과 값을 binding시켜 사용합니다. 여기서 주의할 점은 #name
입니다. 이는 꼭 ngModel
을 적어줘야지 되며, ngModel
을 이용해서 heroForm
과 binding이 되게 됩니다.
이 code의 최고 문제는 html에 error message가 그대로 노출된다는 점입니다. 이를 해결해주기 위해서는 다음과 같이 구성합니다.
Form Validation - new Pattern
새로운 패턴에서 확인할 점은 NgForm
의 생성과 Error Message의 관리 포인트가 Component
로 넘어왔다는 점입니다. 먼저 Component
에서 FormBuilder
를 Import합니다.
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
그리고, ngOnInit
에서 FormGroup
을 생성시켜줍니다.
constructor(private fb: FormBuilder) { }
ngOnInit(): void {
this.buildForm();
}
buildForm(): void {
this.heroForm = this.fb.group({
'name': [this.hero.name, [
Validators.required,
Validators.minLength(4),
Validators.maxLength(24),
]
],
'alterEgo': [this.hero.alterEgo, [ Validators.required ]],
'power': [this.hero.power, Validators.required]
});
this.heroForm.valueChanges
.subscribe(data => this.onValueChanged(data));
this.onValueChanged(); // (re)set validation messages now
}
FormBuilder.group
은 각 Form element name에 따른 Validators
를 갖습니다. Form Validation 조건들을 각 Component
에서 처리가 가능해지는 것입니다. 또한 각 error에 대한 message 처리 역시 Component
에서 가능하게 됩니다. 이는 위 코드 중 onValueChanged
에서 처리 가능합니다.
onValueChanged(data?: any) {
if (!this.heroForm) { return; }
const form = this.heroForm;
for (const field in this.formErrors) {
this.formErrors[field] = "";
const control = form.get(field);
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + " ";
}
}
}
<form [formGroup]="heroForm" *ngIf="active" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" class="form-control" formControlName="name" required>
<div *ngIf="formErrors.name" class="alert alert-danger">
{{ formErrors.name }}
</div>
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" id="alterEgo" class="form-control" formControlName="alterEgo" required>
<div *ngIf="formErrors.alterEgo" class="alert alert-danger">
{{ formErrors.alterEgo }}
</div>
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select id="power" class="form-control" formControlName="power" required>
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
<div *ngIf="formErrors.power" class="alert alert-danger">
{{ formErrors.power }}
</div>
</div>
<button type="submit" class="btn btn-default" [disabled]="!heroForm.valid">Submit</button>
<button type="button" class="btn btn-default" (click)="addHero()">New Hero</button>
</form>
CustomeValidation 처리
CustomValidation의 경우에는 특수한 함수 형태를 반환해야지 됩니다. ValidatorFn
을 반환하는 함수를 작성하고, 그 함수를 FormGroup.group
함수에 Binding시켜주면 됩니다.
import { ValidatorFn } from '@angular/forms';
forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => {
const name = control.value;
const no = nameRe.test(name);
return no ? { 'forbiddenName': { name } } : null;
};
}
this.heroForm = this.fb.group({
'name': [this.hero.name, [
Validators.required,
Validators.minLength(4),
Validators.maxLength(24),
this.forbiddenNameValidator(/bob/i)
]
],
'alterEgo': [this.hero.alterEgo, [ Validators.required ]],
'power': [this.hero.power, Validators.required]
});
Summary
angular2에서의 FormValidation은 많은 발전을 가지고 왔습니다. 가장 큰 변화는 Component
에서 Error Message 및 조건들을 관리하기 편해졌다는 것입니다. 기존 html
에서 처리하는 방법 역시 가지고 있지만, 개인적으로는 최대한 사용하지 않는것이 좋아보입니다.
또한, 다국어 처리에 있어서도 더 나은 방법이 될 수 있습니다.