I've created a library with a directive that injects a service. This library is loaded with a forRoot method in each lazy loaded component where is going to be used.
*** library.module ***
export const SERVICE_INYECTION_TOKEN: InjectionToken<any> = new InjectionToken('service')
export interface IDirectiveModuleConfig {
serviceAdapterConfiguration?: {provider: Provider, moduleName: string};
}
@NgModule({
imports: [
CommonModule
],
declarations: [DirectiveDirective],
exports: [DirectiveDirective]
})
export class LibraryModule {
public static forRoot(config: IDirectiveModuleConfig = {}): ModuleWithProviders<LibraryModule> {
console.log("Library loaded in module " + config.serviceAdapterConfiguration.moduleName)
return {
ngModule: LibraryModule,
providers: [
config.serviceAdapterConfiguration.provider
]
};
}
}
*** directive.directive ***
@Directive({
selector: '[directive]',
})
export class DirectiveDirective implements AfterViewInit {
@Input() methodName: string;
constructor(
private element: ElementRef,
private renderer: Renderer2,
@Inject(SERVICE_INYECTION_TOKEN) private service: any
) {}
ngAfterViewInit(): void {
this.element.nativeElement.innerText += this.service[this.methodName]()
this.renderer.setValue(this.element.nativeElement, this.service[this.methodName]())
}
}
In my main project, I have two lazy-loadeds modules, and each one have a component. One of this modules and its component are lazylodaded by the RouterModules. It works OK
*** app-routing.module ***
const routes: Routes = [
{
path: 'a',
loadChildren: () =>
import('./modules/module-a/module-a.module').then((m) => m.ModuleAModule),
},
{
path: 'b',
loadChildren: () =>
import('./modules/module-b/module-b.module').then((m) => m.ModuleBModule),
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
The other one is created by compileModuleAndAllComponentsAsync() and viewContainerRef.createComponent() in the parent component. It works ok without the service inection, but when I inject the service I get a NullInjectorError.
*** app.component ***
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
@ViewChild("viewContainerRef", { read: ViewContainerRef }) viewContainerRef: ViewContainerRef
component = null;
title = 'component-overview';
constructor(private compiler: Compiler, private injector: Injector) {}
async createModuleAndComponetC() {
const componentInjector: Injector = Injector.create({providers:[{provide:'service', useExisting: ServiceCService}]})
this.viewContainerRef.clear()
const module = (await import('./modules/module-c/module-c.module'))
.ModuleCModule;
this.compiler.compileModuleAndAllComponentsAsync(module).then((factory) => {
factory.ngModuleFactory.create(this.injector);
const componentFactory = factory.componentFactories[0]
const component: ComponentRef<any> = this.viewContainerRef.createComponent(componentFactory);
});
}
}
MODULE A (lazy loaded by routerModule working OK) with its component and service
const serviceConfig: IDirectiveModuleConfig = {
serviceAdapterConfiguration: {
provider: { provide: SERVICE_INYECTION_TOKEN, useClass: ServiceAService },
moduleName: 'A',
}
};
@NgModule({
imports: [
LibraryModule.forRoot(serviceConfig),
CommonModule,
ModuleARoutingModuleModule,
],
declarations: [ComponentAComponent],
exports: [ComponentAComponent],
})
export class ModuleAModule {
constructor(){
console.log("moduleA loaded")
}
}
@Component({
selector: 'app-component-a',
templateUrl: './component-a.component.html',
styleUrls: ['./component-a.component.css'],
})
export class ComponentAComponent implements OnInit {
constructor() {}
ngOnInit() {}
}
@Injectable({
providedIn: 'root'
})
export class ServiceAService {
constructor() { }
serviceA(){
return(" service A!")
}
}
MODULE C (loaded manually with compileModuleAndAllComponentsAsync() and viewContainerRef.createComponent()
export const serviceConfig: IDirectiveModuleConfig = {
serviceAdapterConfiguration: {
provider: { provide: SERVICE_INYECTION_TOKEN, useClass: ServiceCService },
moduleName: 'C',
},
};
@NgModule({
imports: [CommonModule, LibraryModule.forRoot(serviceConfig)],
declarations: [ComponentCComponent],
})
export class ModuleCModule {
constructor() {
console.log('moduleC loaded');
}
static
}
@Component({
selector: 'app-component-c',
templateUrl: './component-c.component.html',
styleUrls: ['./component-c.component.css'],
providers: [ServiceCService],
})
export class ComponentCComponent implements OnInit {
constructor() {
console.log('component C constructor');
}
ngOnInit() {
console.log('component C OnInit');
}
}
@Injectable({
providedIn: 'root',
})
export class ServiceCService {
constructor() {}
serviceC() {
return ' service C!';
}
}
In this example Modules A and B are used with router outlet, and module C is loaded with Compiler and the component is used in a *ngCompilerOutlet
I think that the problem is in the way I load my ComponentC... but I'm a little bit lost...
In adition... i've founded that the module C create a new instance each time I load this, and is not working like singleton...
Finally, I got success!
I saw that I could pass an injector to the viewContainerRef. CreateComponent () method. I tried with the same injector I had used to create the module in the noModuleFactory. Create () method, but it was still wrong.
Finally y realized that NgModule class exports an injector, I suposed this injector provide al the providers in this module and it works ok!!
Now my createModuleAndComponetC() is:
async createModuleAndComponetC() {
this.viewContainerRef.clear();
const module = (await import('./modules/module-c/module-c.module'))
.ModuleCModule;
this.compiler.compileModuleAndAllComponentsAsync(module).then((factory) => {
const module = factory.ngModuleFactory.create(this.injector);
const componentFactory = factory.componentFactories[0];
const component: ComponentRef<any> =
this.viewContainerRef.createComponent(
componentFactory,
0,
module.injector
);
});
}
here is the corrected stackbliz