https://www.learmoreseekmore.com/2022/06/angular14-reactive-forms-example.html
반응형폼을 만들기위해서는 form이 있는 컴포넌트를 포함하는 모듈에 반응형폼 라이브러리를 임포트하고 인스턴스를 만들어야 한다.
form이 있는 컴포넌트 클래스에는 FormControl, FormGroup 라이브러리를 임포트하고 폼구조에 맞게 인스턴스를 생성해야 한다.
템플릿에는 < form >태그에 디렉티브와 클래스 인스턴스바인딩한다.
<form [formGroup]="jobForm" (ngSubmit)="save()">
디렉티브는 대소문자구분하며 formGroup에 FormGroup인스턴스를 바인딩한다.
formGroup디렉티브는 HTML표준 submit이벤트가 발생하면 가로채 ngSubmit이벤트를 발생시킨다.
jobForm = new FormGroup({
firstName : new FormControl(''),
lastName :new FormControl('')
});
템플릿내 폼컨트롤들 바인딩을 위한 인스턴스를 만들어야 한다.
위와같이 폼컨트롤들 인스턴스생성을 간략하해주는 FormBuilder가 있다.
jobForm = this.fb.group({
firstName:[''],
lastName:['']
});
constructor(private fb:FormBuilder){}
FormBuilder를 의존성주입하고 배열형식으로 사용하면 간단해지며 유효성검사도 편리해진다.
모듈화를 위해 중첩그룹을 사용하는것은 일반적이므로 FormBuilder를 사용해 중첩그룹을 작성해보자
fg = this.fb.group({
first:[''],
last:[''],
contacts:this.fb.group({
contactType:['-1'],
email:[''],
phone:['']
})
});
FormBuilder의 group메소드를 사용해 그룹내에 그룹을 정의하면 된다.
템플릿에 위 구조처럼 요소노드를 만들어야 한다.
<form [formGroup]="fg" (ngSubmit)="save()">
</form>
form태그가 첫번째 그룹이 되며 formGroup디렉티브에 FormBuilder의 fg인스턴스를 바인딩한다.
이제부터 fg인스턴스로 하위 폼컨트롤들을 접근할 수 있다.
<form [formGroup]="fg" (ngSubmit)="save()">
<div>firstName : <input type="text" formControlName="first" ></div>
<div>lastName : <input type="text" formControlName="last" ></div>
<div formGroupName="contacts">
<select formControlName="contactType">
<option value="-1">select</option>
<option value="email">email</option>
<option value="phone">phone</option>
</select>
<div>email : <input type="text" formControlName="email" ></div>
<div>phone : <input type="text" formControlName="phone" ></div>
</div>
</form>
[formGroup]이 바인딩됬기 때문에 하위 폼컨트롤들은 바인딩할 필요가 없다.
FormControlName 디렉티브
<form [formGroup]="fg" >
<input type="text" formControlName="phone" >
</form>
FormGroup내부에 있는 폼요소노드를 FormControl인스턴스 이름으로 동기화시킨다.
이것은 바인딩하는 것이 아니고 formGroup하위에 있는 폼요소노드에 접근할 수 있는 통로를 제공한다.
this.fg.controls.first; // firstName 요소노드에 접근이 가능하다.
this.fg.get('first');
this.fg.controls.contacts.controls.contactType;
this.fg.get('contacts.contactType');
클래스코드에서 인스턴스 이름으로 폼요소노드에 접근이 가능하다.
폼컨트롤 동적생성 매커니즘
fg = this.fb.group({
first:['',Validators.required],
last:[''],
contacts:this.fb.group({
contactType:['-1',emailOrPhoneRequired()],
email:[''],
phone:['']
}),
skills:this.fb.array([]) as FormArray,// 빈배열 인스턴스생성, FormArray타입으로 지정하지 않으면 추상폼컨트롤타입으로 된다.
});
addSkill(){
this.fg.controls.skills.push(
this.fb.group({
programLanguage:['',Validators.required],
experience:['']
})
);
}
<ng-container *ngFor="let skill of fg.controls.skills.controls; let i=index">
<div [formGroupName]="i">
<div>programLanguage : <input type="text" formControlName="programLanguage"></div>
<div>experience : <input type="text" formControlName="experience"></div>
<div><button type="button" (click)="deleteSkill(i)">Delete skill</button></div>
</div>
</ng-container>
주의사항
1. *ngFor디렉티브로 반복순회할때 fg.controls.skills.controls로해야 이터레이터가 적용된다.
fg.controls.skills로 사용해도 되지만 조금 간략화하려면 getter함수를 만들어 사용하면 된다.
get skillsForm(){
return this.fg.get('skills') as FormArray;
}
fg.controls.skills.controls를 skillsForm.controls로 줄일 수 있다.
get함수를 사용하면 타입이 추상폼컨트롤타입이 되므로 타입단언을 사용해야 한다.
<ng-container *ngFor="let skill of fg.controls.skills.controls; let i=index">
<div [formGroupName]="i">
<div>programLanguage : <input type="text" formControlName="programLanguage"></div>
<div>experience : <input type="text" formControlName="experience"></div>
<div><button type="button" (click)="deleteSkill(i)">Delete skill</button></div>
</div>
</ng-container>
ngFor 디렉티브는 fg.controls.skills.controls 갯수만큼 반복순회를 한다.
< div formGroupName="i" > 와 < div [formGroupName]="i" > 차이를 이해해야 한다.
< div formGroupName="i" >은 바인딩없이 formGroupName의 동기화를 하는데 i는 블럭범위변수로 반복시마다 증가하기때문에 바인딩을 해야 참조연결이 된다.
폼컨트롤의 유효성검사
fg = this.fb.group({
first:['',Validators.required],
last:[''],
contacts:this.fb.group({
contactType:['-1',emailOrPhoneRequired()],
email:[''],
phone:['']
}),
skills:this.fb.array([]) as FormArray,
});
폼빌더 배열의 두번째 인자에 유효성검사함수를 전달하면 인스턴스는 valid, invalid프로퍼티가 활성화되며 유효성 결과를 boolean값을 가지게 된다.
Validators.required는 앵귤러의 내장 validation메소드다.
emailOrPhoneRequired()는 커스텀 유효성검사 디렉티브이며 유효성검사함수를 반환한다.
export function emailOrPhoneRequired():ValidatorFn{
return (control:AbstractControl):ValidationErrors | null=>{
return control.value == '-1'? {emailOrPhoneRequired:control.value}:null;
}
}
동적생성 폼컨트롤의 유효성검사
동적생성된 입력컨트롤의 유효성검사를 해야하려면 폼컨트롤그룹에 접근하는 방법이 있어야 한다.
getProgramingLagnuage(i:number){
return this.fg.controls.skills.at(i).get('programLanguage');
}
formControlName="programLanguage" 반복되는 그룹마다 같은 이름으로 디렉티브가 동기화되지만 독립적인 그룹으로 구분되기때문에 개별그룹으로 접근후 폼컨트롤에 접근할 수 있다.
<div [formGroupName]="i">
<div>programLanguage : <input type="text" formControlName="programLanguage"></div>
<div *ngIf="getProgramingLagnuage(i)?.invalid && (getProgramingLagnuage(i)?.dirty || getProgramingLagnuage(i)?.touched) " >.......</div>
<div>experience : <input type="text" formControlName="experience"></div>
<div><button type="button" (click)="deleteSkill(i)">Delete skill</button></div>
</div>
*ngIf로 그룹내 폼컨트롤에 접근하여 유효성검사를 할 수 있다.