问题
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