简体   繁体   中英

@NgModule static forRoot when implementing dynamic modules

I have this code in @NgModule:

@NgModule({
 declarations: [
   AppComponent,
   DashboardComponent
 ],
 imports: [
   BrowserModule,
   BrowserAnimationsModule,
   ClarityModule,
   RouterModule.forRoot([
     {
       path: '', redirectTo: 'dashboard', pathMatch: 'full'
     },
     {
       path: 'dashboard', component: DashboardComponent
     },
   ], {useHash: true}),
   LibraryTestModule.forRoot(ServicetestService),
   HttpModule
 ],

If you can see i am injecting the ServicetestService to the LibraryTestModule.

But in my case i am loading this module dynamically using system.js using the code below:

// now, import the new module
    return SystemJS.import(`${url}`).then((module) => {
        console.log(module);

        return SystemJS.import(`${url}`).then((module) => {
            console.log(module);
            return this.compiler.compileModuleAndAllComponentsAsync(module[`${moduleInfo.moduleName}`]).then(compiled => {
                console.log(compiled);
                return module;
            });
        });
    });

Now is there any way to inject the ServiceTestService inside the compileModuleAndAllComponentsAsync method when loading the modules dynamically??

After exploring a while, I think I've arrived at a solution.

widget.module.ts

export const DYNAMIC_CONFIG = new InjectionToken('DYNAMIC_CONFIG');

@NgModule({
  imports: [
    CommonModule,
  ],
  declarations: [WidgetComponent],
  entryComponents: [WidgetComponent]

  providers: [
  ]
})
export class WidgetModule { }

And now let's presume that you're dynamically loading this module into a shell component:

shell.component.ts

ngOnInit () {
    const parentInjector = Injector.create({
        providers: [
            { provide: DYNAMIC_CONFIG, useValue: { message: 'CUSTOM VALUE PROVIDED BY THE CONSUME!' } }
        ],
        parent: this.inj
    });

    import('./widget/widget.module.ts')
        .then(m => {
            this.compiler.compileModuleAndAllComponentsAsync(m.WidgetModule)
                .then(moduleWithCompFactories => {
                    const module = moduleWithCompFactories.ngModuleFactory.create(parentInjector);

                    /**
                     * Adding the components from the lazy-loaded module
                     * into the current view
                     */
                    this.vc.createComponent(moduleWithCompFactories.componentFactories[0], 0, parentInjector);
                })
        })

}

shell.component.html

<ng-container #vc></ng-container>

As you can see, if your dependency (which is provided by the consumer of the module ) will be used across your components that are part of that module and you use the compileModuleAndAllComponentsAsync method, the components won't be able to access that dependency, unless another injector is manually created.

This is because, as you can tell by the name of the method, the components will already be compiled, so you can't add another dependencies on the fly, besides those defined explicitly in the module.


If the components inside the module depend on the provided dependency, you can achieve that by only compiling the module first( compileModuleAsync ) and then compile each component, individually(it might sound tedious, but I can assure you that you will enjoy working with this stuff). This way, they will be able to inject any dynamically provided dependency, event at runtime.

widget.module.ts

@NgModule({
  imports: [
    CommonModule,
    // RouterModule.forChild([{ path: '', component: WidgetComponent }])
  ],
  declarations: [WidgetComponent],

  // No need to use this when using `compileModuleAndAllComponentsAsync`
  entryComponents: [WidgetComponent],
  providers: [
    {
      provide: 'widgets',
      useValue: [
        {
          name: 'widget1',
          component: WidgetComponent,
        },
      ],
    }
  ]
})
export class WidgetModule { }

shell.component.ts

ngOnInit () {
    const parentInjector = Injector.create({
        providers: [
            { provide: DYNAMIC_CONFIG, useValue: { message: 'CUSTOM VALUE PROVIDED BY THE CONSUME!' } }
        ],
        parent: this.inj
    });

    import('./widget/widget.module.ts')
        .then(m => {
            this.compiler.compileModuleAsync(m.WidgetModule)
                .then(factory => {
                    const module = factory.create(parentInjector);

                    const [firstWidget] = module.injector.get('widgets');

                    const componentFactory = module.componentFactoryResolver.resolveComponentFactory(firstWidget.component);

                    this.vc.createComponent(componentFactory);
                });
        })

}

Now, every time you want to add another component to your module, make sure to add it to entryComponents and to your component array so that you can retrieve them using module.injector.get() .

Here is a StackBlitz example .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM