2

I'm trying to inject the FormBuilder service to a dynamic component this way:

Template:

...
<div #vc></div>
...

Component:


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

...

  constructor(private fb: FormBuilder,
    private componentFactoryResolver: ComponentFactoryResolver,
    private _compiler: Compiler, private _injector: Injector,
    private _m: NgModuleRef<any>) {
  }

...

ngAfterViewInit() {
    let  allPms: any[] = null;
    let template = '';

    // construct template on the fly

    const wTypes = this._f.w_type;

    for (const plug of this._plugs) {
      if (plug.name === wTypes) {
        allPms = plug.params;
      }
    }

    for (const pm of allPms) {
      if (pm.type === 'str') {
        template = template + `
        <div class="form-group row">
          <label class="col-sm-3 col-form-label"><strong>` + pm.name + `</strong></label>
          <div class="col-sm-8">
            <input class="form-control" name="` + pm.name + `" type="text"
              formControlName="` + pm.name + `">
          </div>
        </div>
        `;
      }
    }

    // add field for each pm

    let injector1 = Injector.create([
      {
        provide: 'FormBuilder',
        useValue: FormBuilder
      }
    ]);

    const tmpCmp = Component({ template: template, styles: [`label {
      width: 128px;
      margin: 0px 8px;
    }`] })(class {
      constructor(private fb: FormBuilder) {
      }
    });
    const tmpModule = NgModule({ declarations: [tmpCmp] })(class {
    });

    this._compiler.compileModuleAndAllComponentsAsync(tmpModule)
      .then((factories) => {
        const f = factories.componentFactories[0];
        this.cmpRef = f.create(injector1, [], null, this._m);
        this.cmpRef.instance.name = 'B component';
        this._container.insert(this.cmpRef.hostView);
      })
  }

Doing so, I got this error:

  ERROR Error: Can't resolve all parameters for class_1: (?).
    at syntaxError (compiler.js:1021)
    at CompileMetadataResolver.push../node_modules/@angular/compiler/fesm5/compiler.js.CompileMetadataResolver._getDependenciesMetadata (compiler.js:10922)
    at CompileMetadataResolver.push../node_modules/@angular/compiler/fesm5/compiler.js.CompileMetadataResolver._getTypeMetadata (compiler.js:10815)
    at CompileMetadataResolver.push../node_modules/@angular/compiler/fesm5/compiler.js.CompileMetadataResolver.getNonNormalizedDirectiveMetadata (compiler.js:10434)
    at CompileMetadataResolver.push../node_modules/@angular/compiler/fesm5/compiler.js.CompileMetadataResolver.loadDirectiveMetadata (compiler.js:10296)
    at compiler.js:23883
    at Array.forEach (<anonymous>)
    at compiler.js:23882
    at Array.forEach (<anonymous>)
    at JitCompiler.push../node_modules/@angular/compiler/fesm5/compiler.js.JitCompiler._loadModules (compiler.js:23879)
View_testComponent_17 @ testComponent.html:72
push../node_modules/@angular/core/fesm5/core.js.DebugContext_.logError @ core.js:11306
push../node_modules/@angular/core/fesm5/core.js.ErrorHandler.handleError @ core.js:1719
(anonymous) @ core.js:4578
./node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:391
./node_modules/zone.js/dist/zone.js.Zone.run @ zone.js:150
push../node_modules/@angular/core/fesm5/core.js.NgZone.runOutsideAngular @ core.js:3779
push../node_modules/@angular/core/fesm5/core.js.ApplicationRef.tick @ core.js:4578
(anonymous) @ core.js:4462
./node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:391
onInvoke @ core.js:3820

Trying to add a custom injector did not help:

 let injector1 = Injector.create([
  {
    provide: 'FormBuilder',
    useValue: FormBuilder
  }
]);

Here is the Stackblitz that reproduces the issue:

angular-dynamic-components-example

enter image description here

Is there any way to resolve this issue ?

HDJEMAI
  • 9,436
  • 46
  • 67
  • 93
  • 3
    Can you please reproduce this issue in stackblitz? – yurzui Oct 15 '20 at 14:05
  • I think that the error is because you has forgot import `ReactiveFormsModule` in the app.module.ts -well in the module where you has your component-. But you **can not** "inject" in a `[innerHTML]` a html string like you want it. Angular don't "compile at runtime" the .html in a div. – Eliseo Oct 16 '20 at 06:49
  • @yurzui: OK will check if I can create the Stackblitz. – HDJEMAI Oct 16 '20 at 15:15
  • is there any way to use dynamic `import()` and async/await to resolve the missing parameters ? [angular-dynamic-importing-large-libraries](https://medium.com/lacolaco-blog/angular-dynamic-importing-large-libraries-8ec079603d0) – HDJEMAI Oct 17 '20 at 10:20
  • 1
    I'm using this documentation to try to create dynamic component for simple forms, that do not change a lot, the fields to add to the template depend of the JSON object received from back end: [Creating components on the fly](https://indepth.dev/here-is-what-you-need-to-know-about-dynamic-components-in-angular/) – HDJEMAI Oct 17 '20 at 10:28
  • Are you completely tied to building the dynamic component in that exact way you're doing it now? If not, you could adjust your code like I did in [this StackBlitz](https://stackblitz.com/edit/mlc-app-init-ofjruo?file=app/app.component.ts) which shows an alternative approach that works well. – Narm Oct 18 '20 at 22:04
  • @Narm: not sure if it's a best practice for performance and design to inject an entire component with some unrelated business logic to the dynamic component just to satisfy some dependency injection or library dependencies ... – HDJEMAI Oct 19 '20 at 16:23
  • I believe your code will work if you disable the minification for your Angular application. Could you please check that? – Andrei Oct 20 '20 at 22:50
  • @yurzui: StackBlitz is added to the question. – HDJEMAI Oct 21 '20 at 06:49

1 Answers1

1

The ? sign in errors like Can't resolve all parameters for class_1: (?) means that Angular can't resolve type of parameter passed to constructor. In other words, reflector can't recognize that private fb: FormBuilder parameter has FormBuilder type because type dissappers after TypeScript compilation.

In order to tell TS compiler that it should keep this type you need to rewrite this class definition to version with decorator like:

@Component({
  template: template,
  styles: [
    `
      label {
        width: 128px;
        margin: 0px 8px;
      }
    `
  ]
})
class tmpCmp {
  constructor(private fb: FormBuilder) {}
}

Forked Stackblitz

This will be compiled to:

tmpCmp = __decorate([
    core_1.Component({
        template: template,
        styles: [
            `
label {
width: 128px;
margin: 0px 8px;
}
`
        ]
    }),
    __metadata("design:paramtypes", [typeof (_a = typeof forms_1.FormBuilder !== "undefined" && forms_1.FormBuilder) === "function" ? _a : Object])
], tmpCmp);

where you can notice __metadata("design:paramtypes" part which is responsible for providing information to Angular reflector.

There are other ways of solving it.

Static parameters

const tmpCmp = Component({
  ...
})(
  class {
    constructor(private fb: FormBuilder) {}

    static parameters = [ FormBuilder ]
  }
);

Forked Stackblitz

Static ctorParameters method

const tmpCmp = Component({
 ...
})(
  class {
    constructor(private fb: FormBuilder) {}

    static ctorParameters = () => [{ type: FormBuilder} ]
  }
);

Forked Stackblitz

yurzui
  • 205,937
  • 32
  • 433
  • 399