Maneira de injetar serviço FormBuilder angular no componente dinâmico
Estou tentando injetar o serviço FormBuilder em um componente dinâmico desta maneira:
Modelo:
...
<div #vc></div>
...
Componente:
@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);
})
}
Ao fazer isso, recebo este erro:
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
Tentar adicionar um injetor personalizado não ajudou:
let injector1 = Injector.create([
{
provide: 'FormBuilder',
useValue: FormBuilder
}
]);
Aqui está o Stackblitz que reproduz o problema:
exemplo de componentes-angular-dinâmicos
Existe alguma maneira de resolver esse problema?
Respostas
O ?erro de login Can't resolve all parameters for class_1: (?)significa que o Angular não pode resolver o tipo de parâmetro passado ao construtor. Em outras palavras, o reflector não pode reconhecer que o private fb: FormBuilderparâmetro tem FormBuildertipo porque o tipo desaparece após a compilação do TypeScript.
Para informar ao compilador TS que ele deve manter esse tipo, você precisa reescrever essa definição de classe para uma versão com decorador como:
@Component({
template: template,
styles: [
`
label {
width: 128px;
margin: 0px 8px;
}
`
]
})
class tmpCmp {
constructor(private fb: FormBuilder) {}
}
Forked Stackblitz
Isso será compilado para:
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);
onde você pode notar __metadata("design:paramtypes"parte que é responsável por fornecer informações ao refletor Angular.
Existem outras maneiras de resolver isso.
Parâmetros estáticos
const tmpCmp = Component({
...
})(
class {
constructor(private fb: FormBuilder) {}
static parameters = [ FormBuilder ]
}
);
Forked Stackblitz
Método ctorParameters estático
const tmpCmp = Component({
...
})(
class {
constructor(private fb: FormBuilder) {}
static ctorParameters = () => [{ type: FormBuilder} ]
}
);
Forked Stackblitz