简体   繁体   中英

Angular 2/4 : How to add form controls on dynamically created components?

I'm trying to use the ComponentFactoryResolver to create a reactive form.

All the components I want to add in that form are specifics, and implement the ControlValueAccessor interface.

So, my question is simple : How can I add form controls on component created dynamically with ComponentFactoryResolver, without modifying my components ?

At the moment, my code is as follows :

component: ComponentRef<any>;
form: FormGroup;

@ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;

constructor(private resolver: ComponentFactoryResolver, private fb: FormBuilder) {}

ngOnInit(): void {
  this.form = this.fb.group({});
  const component: any = MyStringComponent;
  const factory: any = this.resolver.resolveComponentFactory<any>(component);
  this.component = this.container.createComponent(factory);
}

And the template :

<form
  [formGroup]="form">
  <ng-container #container>

  </ng-container>

</form>

This code works fine, my component is injected where I want, and I can access its Inputs using this.component.instance.

So, where should I add my formControlName directive ?

Having the same use-case, I found this solution posted in 2020 to perfectly solve your issue: https://stackoverflow.com/a/63523127/2879716

The author provides a link to StackBlitz which shows how you can implement a FormControlOutletComponent component which serves as a single entry point for different form controls. As an example, it renders a CustomInputComponent inside, which can be implemented any way you want. The only requirement is that it should implement ControlValueAccessor interface.

It adds one more layer of components nesting, so dynamic component creation actually goes inside of FormControlOutletComponent , not in your main form component. The key code which answers your question is here:

  // in FormControlOutletComponent's decalration
  public ngOnInit(): void {
    // 1. Get NgControl reference (defined by `NG_VALUE_ACCESSOR` provider)
    const ngControl = this.injector.get(NgControl);

    // 2. Resolve dynamic component factory and create a component instance
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(CustomInputComponent);
    const componentRef = this.viewContainerRef.createComponent(componentFactory);

    // 3. Important! Delegate all value-accessor-related work to the instance of the dynamic component
    ngControl.valueAccessor = componentRef.instance;
  }

As a result, your FormControlOutletComponent becomes itself a "proxy" for other dynamic form components.

So, to answer your original question - "where should I add my formControlName directive?", - you should add it (or ngModel directive) to the <app-form-control-outlet> component in your form's HTML template:

<form [formGroup]="form">
  <app-form-control-outlet [formControlName]="'controlName'"></app-form-control-outlet>
  <!-- of course, you can render multiple components this way using ngFor -->
</form>

Note that FormControlOutletComponent doesn't implement ControlValueAccessor interface, though it defines NG_VALUE_ACCESSOR provider:

  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => FormControlOutletComponent),
      multi: true
    }
  ]

It works like a magic. The only inconvenience I'm having with this solution is that I can't simply use standard HTML <input/> as a dynamic component - I have to create my own wrapper around it, with ControlValueAccessor interface, which simply proxies all the commands to the <input/> . If somebody knows how to simplify this, it would be great.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM