잊지 않겠습니다.

angular2 form

angularjs2 2016. 9. 13. 14:03

angular2 form

기존 angularjs에서 문제가 되었던 form이 크게 향상되었습니다. 기존 form의 문제점은 다음과 같습니다.

  1. form의 생성시기를 알 수 없습니다. div tag를 통해서 생성되는 ng-form은 생성시기를 알 수 없기 때문에, form을 검사하기 위해서는 null error를 꼭 check하는 것이 좋습니다.
  2. 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에서 처리하는 방법 역시 가지고 있지만, 개인적으로는 최대한 사용하지 않는것이 좋아보입니다.

또한, 다국어 처리에 있어서도 더 나은 방법이 될 수 있습니다.

Posted by Y2K
,