首页 文章

如何在动态组件中使用反应形式

提问于
浏览
4

背景

我从包含HTML的服务器接收客户端生成的数据,然后我用它来创建一个动态组件,该组件将被注入并显示在我们的客户端中 . The HTML I receive can contain one or many inputs that I need to bind to via Angular Reactive Forms .

尝试1:

我试图通过简单地使用 [innerHTML] 属性并创建动态Reactive Forms来绑定到输入来解决此要求 . 但是,由于使用innerHTML属性的技术限制,该方法失败 . Once the HTML is rendered in the browser all properties are forced to lowercase text so any Angular directives or properties then fail . 比如 *ngIf, *ngFor, [formGroup], formControlName 等...... Angular使用camelCase几乎所有东西,因此一旦它被强制为小写文本,它就会被忽略,这种方法不再是一个可行的解决方案 .

尝试2:

这次我试图利用Angulars NgTemplateOutlet动态地将HTML添加到组件,然后创建并绑定到Reactive Form . This at first seemed like a great solution, but ultimately in order to get the html to render it requires the use of the [innerHTML] property, once again rendering this method useless (as described in my first attempt) .

尝试3:

At last I discovered Dynamic Components ,此解决方案正在部分工作 . 我现在可以成功地创建一个格式良好的Angular HTML模板,该模板在浏览器中正确呈现 . 然而,这只解决了我的一半要求 . At this point the HTML displays as expected, but I have been unable to create a Reactive Form and bind to the inputs .

问题

我现在有一个动态组件生成HTML,其中包含我需要通过创建一个Reactive Form来绑定的输入 .

尝试4:

我尝试将所有逻辑用于在创建的动态组件中创建Reactive Form .

By using this method the dynamic components HTML is displayed, but I get a new error: "ERROR Error: formGroup expects a FormGroup instance. Please pass one in."

StackBlitz with error scenario

2 回答

  • 0

    解决方案

    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. 此解决方案演示了如何创建和使用Reactive Form绑定到动态生成的组件中的输入 .

    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<any>) {
        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, <any>Validators.minLength(minValue), <any>Validators.maxLength(maxValue)]);
              }
              else if ((minValue > 0) && (maxValue === 0)) {
                this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required, <any>Validators.minLength(minValue)]);
              }
              else if ((minValue === 0) && (maxValue > 0)) {
                this.group[dyclass.Key] = new FormControl(dyclass.defaultValue || '', [Validators.required, <any>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 = `  <div style="border: solid; border-color:green;">
                          <p>This is a dynamic component with an input using a reactive form </p>
                          <form [formGroup]="_parent.ackStringForm" class="form-row">
                          <input type="text" formControlName="formTextField"  required> 
                          <div *ngIf="_parent.formErrors.formTextField" class="alert alert-danger">
                          {{ _parent.formErrors.formTextField }}</div>
                          </form><br>
                          <button (click)="_parent.submitForm()"> Submit</button>
                          <br>
                          </div>
                          <br>
                          `;
        @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);
      }
    }
    
  • 1

    如果我正确读取此内容,您的模板(HTML)将超出组件初始化,特别是在 FormGroup 上 . 防止这种情况发生的最好方法是将 *ngIf 语句附加到您绑定 FormGroup 的表单上 . 这样,在定义 FormGroup 之前,它不会呈现 .

    <form *ngIf="ackStringForm" [formGroup]="ackStringForm" novalidate>
    

相关问题