Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular5: Dynamic component loading - StaticInjector error with @angular/core dependencies

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

like image 216
Thibaud Arnault Avatar asked Oct 29 '22 23:10

Thibaud Arnault


1 Answers

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

like image 169
Thibaud Arnault Avatar answered Nov 11 '22 13:11

Thibaud Arnault