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

时间:2016-10-24 06:11:40

标签: angular typescript angular2-directives angular2-forms

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

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

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

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

以下是我已经实施的独立Angular2应用的简要示例,其中显示了在启动时以编程方式添加的组件(CustomComponent)。为了将CustomComponent绑定到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 {
}

1 个答案:

答案 0 :(得分:2)

如果我不误解你的需要......

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

  1. 从JSON config
  2. 生成模板的人
  3. 另一个从JSON config
  4. 生成表单的人

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

    @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;
      }
    
    }
    

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

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