本文將通過一個(gè)簡(jiǎn)單的例子簡(jiǎn)單展示Angular動(dòng)態(tài)組件+響應(yīng)式表單的使用。
【關(guān)鍵字】: Angular哎榴,動(dòng)態(tài)組件琼蚯,響應(yīng)式表單,ComponentFactoryResolver谋竖,ViewContainerRef红柱,ReactiveFormsModule
最終代碼在最后
- 新建一個(gè)項(xiàng)目
ng new dynamic-form
- 新建一個(gè)模塊(并在app.module中導(dǎo)入DynamicFormModule)
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [ ],
imports: [
CommonModule,
ReactiveFormsModule,
],
entryComponents: [ ],
exports: [ ]
})
export class DynamicFormModule { }
- 現(xiàn)在,我們需要?jiǎng)?chuàng)建用于創(chuàng)建動(dòng)態(tài)表單的容器
動(dòng)態(tài)表單的入口點(diǎn)是主容器蓖乘。這將是我們的動(dòng)態(tài)表單模塊公開的唯一組件锤悄,負(fù)責(zé)接受表單配置并創(chuàng)建表單。
// dynamic-form-module components/dyanmic-form.component
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { FromItemConfig } from '../../models/dynamic-form.model';
@Component({
selector: 'app-dynamic-form',
templateUrl: './dynamic-form.component.html',
styleUrls: ['./dynamic-form.component.scss']
})
export class DynamicFormComponent implements OnInit {
@Input()
public formConfigs: Array<FromItemConfig> = new Array<FromItemConfig>();
@Output()
public readonly submitted: EventEmitter<any> = new EventEmitter<any>();
public formGroup: FormGroup;
public constructor(private fb: FormBuilder ) {
this.formGroup = this.fb.group({});
}
public ngOnInit(): void {
this.setFromControl();
}
public submitHandler(): void {
// tslint:disable-next-line: forin
for (const i in this.formGroup.controls) {
this.formGroup.controls[i].markAsDirty();
this.formGroup.controls[i].updateValueAndValidity();
}
if (this.formGroup.valid) {
this.submitted.emit(this.formGroup.value);
}
}
private setFromControl(): void {
this.formConfigs?.forEach((control, idx) => {
this.formGroup.addControl(control.controlName,
this.fb.control(control.defaultValue || null, control?.required ? Validators.required : null));
});
}
}
由于我們的表單是動(dòng)態(tài)表單嘉抒,因此我們需要接受一個(gè)配置數(shù)組才能知道要?jiǎng)?chuàng)建什么零聚。為此,我們正在使用,@Input()它接受任何對(duì)象數(shù)組隶症。
對(duì)于配置中的每個(gè)項(xiàng)目政模,我們都希望該對(duì)象包含一些必要屬性,所以我們頂一個(gè)一個(gè)接口FromItemConfig
export interface FromItemConfig<T = any> {
type: FromItemType;
controlName: string;
label: string;
defaultValue?: T;
disabled?: boolean;
required?: boolean;
options?: Array<OptionItem>;
}
export interface OptionItem {
value: any;
label: string;
}
// 這里根據(jù)我們已經(jīng)支持的表單控件類型定義了一個(gè)枚舉
export enum FromItemType {
'checkbox' = 'checkbox',
'input' = 'input',
'number' = 'number',
'textarea' = 'textarea',
'radio' = 'radio',
// ...
}
- 在
app.componet.html
中使用我們創(chuàng)建的dynamic-form
<app-dynamic-form [formConfigs]="formConfigs" (submitted)="submitHandler($event)"></app-dynamic-form>
public formConfigs: Array<FromItemConfig> = [
{
type: FromItemType.input,
controlName: 'name',
label: '姓名',
required: true
},
{
type: FromItemType.radio,
controlName: 'sex',
label: '性別',
defaultValue: 'man',
options: [{ label: '男', value: 'man' }, { label: '女', value: 'woman' }, { label: '未知', value: 'unknown' }]
},
{
type: FromItemType.input,
controlName: 'nationality',
label: '民族',
defaultValue: '漢'
},
{
type: FromItemType.number,
controlName: 'age',
label: '年齡',
},
{
type: FromItemType.input,
controlName: 'number',
label: '學(xué)號(hào)',
},
{
type: FromItemType.input,
controlName: 'class',
label: '班級(jí)',
},
{
type: FromItemType.textarea,
controlName: 'class',
label: '愛好',
},
{
type: FromItemType.checkbox,
controlName: 'reConfirm',
label: '已確認(rèn)',
required: true
}
]
- 根據(jù)我們的需要去創(chuàng)建我們需要的表單項(xiàng)
// ***/dynamic-form/elements/
ng g c input
ng g c radio
...
這里以Input為例(這里我還用了ng-zorro)蚂会,其他的可以看源碼淋样。
<nz-form-item [formGroup]="formGroup">
<nz-form-label [nzSpan]="6" nzFor="email" [nzRequired]="formConfig?.required">{{formConfig.label}}</nz-form-label>
<nz-form-control [nzSpan]="14" nzErrorTip="該項(xiàng)為必填項(xiàng)">
<input nz-input [placeholder]="formConfig.label" [formControlName]="formConfig.controlName"
[name]="formConfig.controlName">
</nz-form-control>
</nz-form-item>
import { Component, OnInit } from '@angular/core';
import { FormElementBaseClass } from '../../models/form-element-base.class';
import { FromItemConfig } from '../../models/dynamic-form.model';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'app-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.scss']
})
export class InputComponent {
public formConfig: FromItemConfig;
public formGroup: FormGroup;
public get formControl(): { [key: string]: any } {
return this.formGroup.controls;
}
public constructor() { }
}
- 現(xiàn)在最關(guān)鍵的是dynamic-form.component 的實(shí)現(xiàn)
<div class="dynamic-form-container" (ngSubmit)="submitHandler()">
<form class="dynamic-form" nz-form [formGroup]="formGroup">
<ng-container *ngFor="let formConfig of formConfigs">
<div appDynamicForm [formConfig]="formConfig" [formGroup]="formGroup"></div>
</ng-container>
<nz-form-item nz-row class="submit-area" *ngIf="formConfigs?.length">
<nz-form-control [nzSpan]="14" [nzOffset]="6">
<button nz-button nzType="primary" (click)="submitHandler()">Submit</button>
</nz-form-control>
</nz-form-item>
</form>
</div>
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { FromItemConfig } from '../../models/dynamic-form.model';
@Component({
selector: 'app-dynamic-form',
templateUrl: './dynamic-form.component.html',
styleUrls: ['./dynamic-form.component.scss']
})
export class DynamicFormComponent implements OnInit {
@Input()
public formConfigs: Array<FromItemConfig> = new Array<FromItemConfig>();
public formGroup: FormGroup;
@Output()
public readonly submitted: EventEmitter<any> = new EventEmitter<any>();
public constructor(
private fb: FormBuilder,
) {
this.formGroup = this.fb.group({});
}
public ngOnInit(): void {
this.setFromControl();
}
public submitHandler(): void {
// tslint:disable-next-line: forin
for (const i in this.formGroup.controls) {
this.formGroup.controls[i].markAsDirty();
this.formGroup.controls[i].updateValueAndValidity();
}
if (this.formGroup.valid) {
this.submitted.emit(this.formGroup.value);
}
}
private setFromControl(): void {
this.formConfigs?.forEach((control, idx) => {
this.formGroup.addControl(control.controlName,
this.fb.control(control.defaultValue || null, control?.required ? Validators.required : null));
});
}
}
- 從上面的代碼中,我們可以看到一個(gè)自定義指令
appDynamicForm
胁住,這個(gè)自定義指令是最終實(shí)現(xiàn)的一個(gè)關(guān)鍵趁猴。 componentFactoryResolver(動(dòng)態(tài)組件加載器) 和 ViewContainerRef(視圖容器)是我們實(shí)現(xiàn)的根本。
import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Directive, Input, OnInit, ViewContainerRef } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { NumberComponent } from '../elements/number/number.component';
import { RadioGroupComponent } from '../elements/radio-group/radio-group.component';
import { FromItemConfig } from '../models/dynamic-form.model';
import { CheckboxComponent } from './../elements/checkbox/checkbox.component';
import { InputComponent } from './../elements/input/input.component';
import { TextareaComponent } from './../elements/textarea/textarea.component';
// 通過這個(gè)常量將類型和最終指向的組件對(duì)應(yīng)起來(lái)
const elementComponent: { [key: string]: any } = {
checkbox: CheckboxComponent,
input: InputComponent,
textarea: TextareaComponent,
radio: RadioGroupComponent,
number: NumberComponent,
};
@Directive({
selector: '[appDynamicForm]'
})
export class DynamicFormDirective implements OnInit {
@Input()
public formConfig: FromItemConfig;
@Input()
public formGroup: FormGroup;
private component: ComponentRef<any>;
public constructor(
// ComponentFactoryResolver && ViewContainerRef
private componentFactoryResolver: ComponentFactoryResolver,
private viewContainerRef: ViewContainerRef,
) { }
public ngOnInit(): void {
this.viewContainerRef.clear();
if (!this.formConfig) {
return;
}
const component: any = elementComponent[this.formConfig.type];
if (!component) {
return;
}
const componentFactory: ComponentFactory<any> = this.componentFactoryResolver.resolveComponentFactory(component);
const componentRef: ComponentRef<any> = this.viewContainerRef.createComponent(componentFactory);
componentRef.instance.formConfig = this.formConfig;
componentRef.instance.formGroup = this.formGroup;
}
}
至此彪见,動(dòng)態(tài)響應(yīng)式表單的實(shí)現(xiàn)就基本完成了(過程講的比較粗略)儡司,有問題歡迎討論??
?? 查看所有代碼