I am migrating an electron application from AngularJS to Angular5 and I am facing issues in a part where I need to load an arbitrary module with its components and render the main component to the view dynamically (it's an addon system). In my application, an addon is an angular5 module containing a component which is rendered in my angular5 application.
I load dynamically the component like this:
@ViewChild("view", { read: ViewContainerRef })
view: ViewContainerRef;
constructor(
// ...
private compiler: Compiler,
private injector: Injector,
private moduleRef: NgModuleRef<any>
) {}
then,
const addonModule = addonObject.getModule();
this.compiler
.compileModuleAndAllComponentsAsync(addonModule)
.then(factories => {
const factory = factories.componentFactories.find(
componentFactory =>
addonObject.getViewComponent().name ==
componentFactory.componentType.name
);
if (factory) {
const cmpRef = factory.create(this.injector, [], null, this.moduleRef);
cmpRef.instance.config = {
from: "[email protected]",
apikey: "dsfkjd"
};
this.view.insert(cmpRef.hostView);
}
});
it works great but when I add Material Component in the template, it fails at factory.create()
with this error:
template:
<div>
<button mat-button>test</button>
</div>
error:
ERROR Error: Uncaught (in promise): Error: StaticInjectorError[Renderer2]:
StaticInjectorError[Renderer2]:
NullInjectorError: No provider for Renderer2!
or
template:
<div fxLayout="row" fxFlex>
test
</div>
error:
ERROR Error: Uncaught (in promise): Error: StaticInjectorError[MediaMonitor]:
StaticInjectorError[MediaMonitor]:
NullInjectorError: No provider for MediaMonitor!
The Material modules seems well imported in my addon module else if it's not loaded I have different behaviors: it ignores directives (e.g. <button mat-button></button>
is just a <button></button>
without material effect and without errors) and material component throw errors (e.g. <mat-card>test<mat-card>
throws Error: Template parse errors: 'mat-card' is not a known element...
)
I have tried to bundle the dynamic module in many different ways: tsc / ngc / ng-packagr / webpack.
The bundle is working when I import it normally (static) in the NgModule of another app built with ng-cli.
Does someone know how to render dynamically a component which has material component/directive used in the template like fxFlex / mat-button / mat-card ?
EDIT: I have made a reproduction of the bug with SystemJS here: https://github.com/thyb/repro-dynamic-loading
Angular: 5.0.2
Angular Material: 5.0.0-rc1
As @yurzui explained in comments, the @angular/* dependencies were loaded twice. As a workaround, the only way I found to make it work is by using window
to share the @angular objects between the application and the addons (@angular/core
, @angular/common
, @angular/material
).
in my application index.html:
window.angular = {
core: require("@angular/core"),
common: require("@angular/common"),
material: require("@angular/material"),
...
}
and in my addons:
const NgModule = (window as any).angular.core.NgModule;
const CommonModule = (window as any).angular.common.CommonModule;
const MatButtonModule = (window as any).angular.material.MatButtonModule;
...
It's not really elegant (bye bye tree shaking) but at least it works... If someone has a simpler solution, I take it :)
I came across a discussion on Gitter about @angular/element which seems promising as it creates standalone/self contained web component which could be injected dynamically without the issues I encountered - https://www.youtube.com/watch?v=ljsOPm4MMEo&feature=youtu.be&t=13m20s. This is a lab project so not available in the current releases but there is already some work on it: https://github.com/angular/angular/pull/19469
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With