美文网首页
Angular动态组件&响应式表单的实现[附源码]

Angular动态组件&响应式表单的实现[附源码]

作者: 五月烧 | 来源:发表于2020-03-15 15:55 被阅读0次

    本文将通过一个简单的例子简单展示Angular动态组件+响应式表单的使用。
    【关键字】: Angular,动态组件,响应式表单,ComponentFactoryResolver,ViewContainerRef,ReactiveFormsModule
    最终代码在最后

    • 新建一个项目
    ng new dynamic-form
    
    • 新建一个模块(并在app.module中导入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 { }
    
    • 现在,我们需要创建用于创建动态表单的容器
      动态表单的入口点是主容器。这将是我们的动态表单模块公开的唯一组件,负责接受表单配置并创建表单。
    // 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));
            });
        }
    }
    

    由于我们的表单是动态表单,因此我们需要接受一个配置数组才能知道要创建什么。为此,我们正在使用,@Input()它接受任何对象数组。
    对于配置中的每个项目,我们都希望该对象包含一些必要属性,所以我们顶一个一个接口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;
    }
    // 这里根据我们已经支持的表单控件类型定义了一个枚举
    export enum FromItemType {
        'checkbox' = 'checkbox',
        'input' = 'input',
        'number' = 'number',
        'textarea' = 'textarea',
        'radio' = 'radio',
        // ...
    }
    
    • app.componet.html中使用我们创建的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: '学号',
                },
                {
                    type: FromItemType.input,
                    controlName: 'class',
                    label: '班级',
                },
                {
                    type: FromItemType.textarea,
                    controlName: 'class',
                    label: '爱好',
                },
                {
                    type: FromItemType.checkbox,
                    controlName: 'reConfirm',
                    label: '已确认',
                    required: true
                }
    ]
    
    • 根据我们的需要去创建我们需要的表单项
    // ***/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="该项为必填项">
        <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() { }
    }
    
    • 现在最关键的是dynamic-form.component 的实现
    <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));
            });
        }
    }
    
    • 从上面的代码中,我们可以看到一个自定义指令 appDynamicForm,这个自定义指令是最终实现的一个关键。 componentFactoryResolver(动态组件加载器) 和 ViewContainerRef(视图容器)是我们实现的根本。
    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';
    
    // 通过这个常量将类型和最终指向的组件对应起来
    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;
        }
    }
    

    至此,动态响应式表单的实现就基本完成了(过程讲的比较粗略),有问题欢迎讨论😊
    🤞 查看所有代码

    相关文章

      网友评论

          本文标题:Angular动态组件&响应式表单的实现[附源码]

          本文链接:https://www.haomeiwen.com/subject/xhlfehtx.html