angular - 如何将动态添加的自定义组件注册为表单组中的表单控件

标签 angular typescript angular2-directives angular2-forms

我创建了一些自定义组件,每个组件都实现了 ControlValueAccessor ,以便它们可以作为 FormGroup 中的控件。通常,唯一剩下要做的就是将 formControlName 指令添加到 HTML 中的每个自定义组件。

但是,我在运行时使用 this 将这些组件附加到表单中技术。

我的问题是我无法使用包含的 FormGroup 注册这些组件,因为我无法使用动态添加的 formControlName 指令(或任何指令,就此而言)控制。

有没有其他人发现这是怎么可能的?目前,我将每个控件包装在另一个组件中,以便我可以使用 formControlName 指令,但是这个太丑陋而且劳动强度大。


下面是我已经实现为独立 Angular2 应用程序的精简示例,它显示了在启动时以编程方式添加的组件 (CustomComponent)。为了将 CustomComponent 绑定(bind)到 FormGroup,我不得不创建我希望避免的 CustomContainerComponent

import {
  Component, ReflectiveInjector, ViewContainerRef, Compiler, NgModule, ModuleWithComponentFactories,
  OnInit, ViewChild, forwardRef
} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {NG_VALUE_ACCESSOR, ControlValueAccessor, FormGroup, FormBuilder, ReactiveFormsModule} from '@angular/forms';


export class AbstractValueAccessor<T> implements ControlValueAccessor {
  _value: T;
  get value(): T {
    return this._value;
  };

  set value(v: T) {
    if (v !== this._value) {
      this._value = v;
      this.onChange(v);
    }
  }

  writeValue(value: T) {
    this._value = value;
    this.onChange(value);
  }

  onChange = (_) => {};
  onTouched = () => {};

  registerOnChange(fn: (_: any) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

}

export function MakeProvider(type: any) {
  return {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => type),
    multi: true
  };
}

@Component({
  selector: 'app-custom',
  template: `<input type="text" [value]="value">`,
  providers: [MakeProvider(CustomComponent)]
})
class CustomComponent extends AbstractValueAccessor<string> {

}

@Component({
  selector: 'app-custom-container',
  template: `<div [formGroup]="formGroup"><app-custom formControlName="comp"></app-custom></div>`
})
class CustomContainerComponent {
  formGroup: FormGroup;
}

@NgModule({
  imports: [BrowserModule, ReactiveFormsModule],
  declarations: [CustomComponent, CustomContainerComponent]
})
class DynamicModule {
}

@Component({
  selector: 'app-root',
  template: `<h4>Dynamic Components</h4><br>
             <form [formGroup]="formGroup">
               <div #dynamicContentPlaceholder></div>
             </form>`
})
export class AppComponent implements OnInit {

  @ViewChild('dynamicContentPlaceholder', {read: ViewContainerRef})
  public readonly vcRef: ViewContainerRef;

  factory: ModuleWithComponentFactories<DynamicModule>;
  formGroup: FormGroup;

  constructor(private compiler: Compiler, private formBuilder: FormBuilder) {
  }

  ngOnInit() {
    this.compiler.compileModuleAndAllComponentsAsync(DynamicModule)
      .then((moduleWithComponentFactories: ModuleWithComponentFactories<DynamicModule>) => {
        this.factory = moduleWithComponentFactories;
        const compFactory = this.factory.componentFactories.find(x => x.selector === 'app-custom-container');
        const injector = ReflectiveInjector.fromResolvedProviders([], this.vcRef.parentInjector);
        let cmp = this.vcRef.createComponent(compFactory, this.vcRef.length, injector, []);
        (<CustomContainerComponent>cmp.instance).formGroup = this.formGroup;
      });

    this.formGroup = this.formBuilder.group({
      'comp': ['hello world']
    })
  }
}

@NgModule({
  imports: [BrowserModule, ReactiveFormsModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {
}

最佳答案

如果我没有误解你的需求...

奇怪的是,您将表单组附加到 AppComponent 中。相反,您可以动态创建完整的表单。为此,您需要实现两个功能:

  1. 从 JSON 配置生成模板
  2. 另一个从 JSON 配置生成表单

然后简单地构建一个包含所有输入的大型表单容器组件。我会推荐使用这里的服务:

@Injectable()
export class ModuleFactory {

  public createComponent(jsonConfig: SomeInterface) {
    @Component({
      template: `
        <div ngForm="form">${ /* and here generate the template containing all inputs */ }</div>
      `
    })
    class FormContainerComponent {
      public form: FormGroup = /* generate a form here */;
    };

    return FormContainerComponent;
  }

  public createModule(component: any) {
    @NgModule({
      imports: [
        CommonModule,
        ReactiveFormsModule
      ],
      declarations: [ component ]
    })
    class MyModule {};

    return MyModule;
  }

}

这当然只是一个简化版本,但它适用于我的项目,不仅适用于普通表单,而且适用于嵌套表单组/数组/控件。

注入(inject)服务后,您可以使用这两个函数生成模块和组件。

关于angular - 如何将动态添加的自定义组件注册为表单组中的表单控件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40211719/

相关文章:

javascript - 支持十进制数的 Number 指令

javascript - 如何根据另一个可观察值的结果创建新的可观察值?

angular - 从 ts (angular, ng-select) 打开选择

Typescript 返回类型取决于对象参数

typescript - 为什么应该在对象查找中使用 "as keyof typeof lookup"?

angular - 如何以最有效的方式提取ViewContainerRef和ComponentRef?

angular 2 Routes 3.0,区分大小写

javascript - nginx 1.14.0 Ubuntu 上的 400 错误请求

android - FCM 通知 - 多个设备

html - Angular2 - 将 CSS 类添加到选定元素