Way to inject angular FormBuilder service to dynamic component

别来无恙 提交于 2020-11-28 09:16:59

问题


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

Is there any way to resolve this issue ?


回答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



来源:https://stackoverflow.com/questions/64333595/way-to-inject-angular-formbuilder-service-to-dynamic-component

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!