How to use reactive forms in a dynamic component

后端 未结 2 1735
难免孤独
难免孤独 2020-12-03 19:49

Background

I receive client generated data from the server that contains HTML that I then use to create a dynamic component that gets injected and displayed in our c
2条回答
  •  庸人自扰
    2020-12-03 20:04

    The Solution

    Working StackBlitz with solution

    The solution is to create the Reactive Form in the parent component. Then use Angulars dependency injection and inject the parent component into the Dynamic Component.

    By injecting the parent component into the dynamic component you will have access to all of the parents components public properties including the reactive form. This solution demonstrates being able to create and use a Reactive Form to bind to the input in a dynamically generated component.

    Full code below

    import {
      Component, ViewChild, OnDestroy,
      AfterContentInit, ComponentFactoryResolver,
      Input, Compiler, ViewContainerRef, NgModule,
      NgModuleRef, Injector, Injectable
    } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import {
      ReactiveFormsModule, FormBuilder,
      FormGroup, FormControl, Validators
    } from '@angular/forms';
    
    
    @Injectable()
    export class DynamicControlClass {
      constructor(public Key: string,
        public Validator: boolean,
        public minLength: number,
        public maxLength: number,
        public defaultValue: string,
        public requiredErrorString: string,
        public minLengthString: string,
        public maxLengthString: string,
        public ControlType: string
      ) { }
    }
    
    @Component({
      selector: 'app',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements AfterContentInit, OnDestroy {
      @ViewChild('dynamicComponent', { read: ViewContainerRef }) _container: ViewContainerRef;
      public ackStringForm: FormGroup;
      public ctlClass: DynamicControlClass[];
      public formErrors: any = {};
      public group: any = {};
      public submitted: boolean = false;
    
      private cmpRef;
    
      constructor(
        private fb: FormBuilder,
        private componentFactoryResolver: ComponentFactoryResolver,
        private compiler: Compiler,
        private _injector: Injector,
        private _m: NgModuleRef) {
        this.ctlClass = [
          new DynamicControlClass('formTextField', true, 5, 0, '', 'Please enter a value', 'Must be Minimum of 5 Characters', '', 'textbox')]
      }
    
      ngOnDestroy() {
        //Always destroy the dynamic component
        //when the parent component gets destroyed
        if (this.cmpRef) {
          this.cmpRef.destroy();
        }
      }
    
      ngAfterContentInit() {
        this.ctlClass.forEach(dyclass => {
          let minValue: number = dyclass.minLength;
          let maxValue: number = dyclass.maxLength;
    
          if (dyclass.Validator) {
            this.formErrors[dyclass.Key] = '';
    
            if ((dyclass.ControlType === 'radio') || (dyclass.ControlType === 'checkbox')) {
              this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || null, [Validators.required]);
            }
            else {
              if ((minValue > 0) && (maxValue > 0)) {
                this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required, Validators.minLength(minValue), Validators.maxLength(maxValue)]);
              }
              else if ((minValue > 0) && (maxValue === 0)) {
                this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required, Validators.minLength(minValue)]);
              }
              else if ((minValue === 0) && (maxValue > 0)) {
                this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required, Validators.maxLength(maxValue)]);
              }
              else if ((minValue === 0) && (maxValue === 0)) {
                this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required]);
              }
            }
          }
          else {
            this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '');
          }
        });
    
        this.ackStringForm = new FormGroup(this.group);
    
        this.ackStringForm.valueChanges.subscribe(data => this.onValueChanged(data));
    
        this.onValueChanged();
    
        this.addComponent();
      }
    
      private addComponent() {
        let template = `  

    This is a dynamic component with an input using a reactive form

    {{ _parent.formErrors.formTextField }}



    `; @Component({ template: template, styleUrls: ['./dynamic.component.css'] }) class DynamicComponent { constructor(public _parent: AppComponent) {} } @NgModule({ imports: [ ReactiveFormsModule, BrowserModule ], declarations: [DynamicComponent] }) class DynamicComponentModule { } const mod = this.compiler.compileModuleAndAllComponentsSync(DynamicComponentModule); const factory = mod.componentFactories.find((comp) => comp.componentType === DynamicComponent ); const component = this._container.createComponent(factory); } private onValueChanged(data?: any) { if (!this.ackStringForm) { return; } const form = this.ackStringForm; for (const field in this.formErrors) { // clear previous error message (if any) this.formErrors[field] = ''; const control = form.get(field); if ((control && control.dirty && !control.valid) || (this.submitted)) { let objClass: any; this.ctlClass.forEach(dyclass => { if (dyclass.Key === field) { objClass = dyclass; } }); for (const key in control.errors) { if (key === 'required') { this.formErrors[field] += objClass.requiredErrorString + ' '; } else if (key === 'minlength') { this.formErrors[field] += objClass.minLengthString + ' '; } else if (key === 'maxLengthString') { this.formErrors[field] += objClass.minLengthString + ' '; } } } } } public submitForm(){ let value = this.ackStringForm.value.formTextField; alert(value); } }

提交回复
热议问题