앵귤러 개발을 하다보면 ReactiveForms 모듈을 Component에서 종종 사용해야할때가 있다.
특히 Form의 유효성 검사가 복잡해지다 보면 다른 View 영역을 제어하는 것 보다 Form을 제어하는 코드가 더 많아질때가 많다.
Html 코드
<h1>Form Test</h1>
<form [formGroup]="form">
<label for="first">First</label>
<input id="first" formControlName="firstName">
<hr />
<label for="second">Second</label>
<input id="second" formControlName="secondName">
</form>
<ng-container *ngIf="form.errors">
{{form.errors.message}}
</ng-container>
컴포넌트 코드
import { Component, OnInit } from '@angular/core';
import {
AbstractControl,
FormBuilder,
FormGroup,
ValidationErrors
} from '@angular/forms';
@Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css']
})
export class FormComponent implements OnInit {
form = this.fb.group(
{
firstName: [''],
secondName: ['']
},
{
validators: [this.checkTwoName]
}
);
constructor(private fb: FormBuilder) {}
ngOnInit() {}
private checkTwoName(nameGroup: AbstractControl): ValidationErrors | null {
const firstName = (nameGroup as FormGroup).controls.firstName;
const secondName = (nameGroup as FormGroup).controls.secondName;
if (firstName.pristine || secondName.pristine) {
return null;
}
if (firstName.value === secondName.value) {
return {
message: '첫번째 이름과 두번째 이름이 같습니다.'
};
}
return null;
}
}
사실 예제코드가 많이 부족하다. 😅 (원래는 이보다 더 많은 유효성 검사를 체크하는 함수들이 서로 엮여있다.)
부족한 부분은 Form 말고도 다른 View를 제어하는 코드가 있다고 상상으로 채워넣길 바란다.
이렇게 Form을 제어하는 코드가 많아지게 되면 이 Component는 Form을 제어하는 관심사와, View를 제어하는 2개의 관심사를 가지게 된다.
이는 Directive를 사용하면 쉽게 분리할 수 있다.
Directive를 적용한 HTML
<h1>Form Test</h1>
<form [formGroup]="form" appFormControlExample>
<label for="first">First</label>
<input id="first" formControlName="firstName">
<hr />
<label for="second">Second</label>
<input id="second" formControlName="secondName">
</form>
<ng-container *ngIf="form.errors">
{{form.errors.message}}
</ng-container>
Form을 제어하는 Directive
import { Directive, Input } from '@angular/core';
import { AbstractControl, FormGroup, ValidationErrors } from '@angular/forms';
@Directive({
selector: '[appFormControlExample][formGroup]' // appFormControlExample Directive가 적용되면서 formGroup property Binding이 적용된 경우에만 붙는다.
})
export class FormControlExampleDirective {
@Input()
formGroup: FormGroup;
constructor() {}
ngOnInit() {
this.formGroup.setValidators([this.checkTwoName]);
}
private checkTwoName(nameGroup: AbstractControl): ValidationErrors | null {
const firstName = (nameGroup as FormGroup).controls.firstName;
const secondName = (nameGroup as FormGroup).controls.secondName;
if (firstName.pristine || secondName.pristine) {
return null;
}
if (firstName.value === secondName.value) {
return {
message: '첫번째 이름과 두번째 이름이 같습니다.'
};
}
return null;
}
}
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
@Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css']
})
export class FormComponent implements OnInit {
form = this.fb.group({
firstName: [''],
secondName: ['']
});
constructor(private fb: FormBuilder) {}
ngOnInit() {}
}
컴포넌트에서 깔끔하기 Form의 관심사를 분리할 수 잇다.
공부하면서 알아낸건데 FormControl가 붙은 엘리먼트에 Directive를 붙이면 NgControl을 통해서 접근, 제어가 가능하다.
<form [formGroup]="form" appFormControlExample>
<label for="first">First</label>
<!-- appControlInput Directive를 새롭게 부착함 -->
<input id="first" formControlName="firstName" appControlInput>
<hr />
<label for="second">Second</label>
<input id="second" formControlName="secondName">
</form>
<ng-container *ngIf="form.errors">
{{form.errors.message}}
</ng-container>
import { Directive } from '@angular/core';
import { NgControl } from '@angular/forms';
@Directive({
selector: '[appControlInput]'
})
export class ControlInputDirective {
// FormControl이 NgControl으로 DI되어서 접근 가능함.
constructor(private ngControl: NgControl) {}
ngOnInit() {
this.ngControl.valueChanges.subscribe(data => {
console.log(data);
});
}
}
FormControl만 가능하고 FormGroup은 불가능하다.