1

I am currently using Angular 4's asynchronous compiler to dynamically load an external module and add one of its components into the DOM using NgComponentOutlet. The external module is meant to be functionally disjoint from the main application, and as such it should have its own, unique dependency injector (or rather, an injector without access to the providers of the parent module). However, if I create a new dependency injector using ReflectiveInjector.resolveAndCreate, NgComponentOutlet fails with the error No provider for NgModuleRef!.

After some tracing through NgComponentOutlet, this seems to be because the NgModuleFactory depends on renderers, etc. in order to create the component. However, I've been unable to find an API that exposes a minimal dependency injector akin to the one provided to the root module. Is there any way to obtain this injector, or to create a minimal dependency injector that still contains the necessary resources for rendering a dynamically compiled component?

colavitam
  • 1,312
  • 1
  • 8
  • 16
  • show your relevant code, _ load an external module and add it into the DOM using NgComponentOutlet._ - do you mean add component from it? you can't add module to `ngComponentOutlet` – Max Koretskyi Jul 24 '17 at 07:32
  • Rather, an external module is compiled using an injected instance of Compiler. The compiled module is provided to ngComponentOutlet from which it constructs one of its components. `` – colavitam Jul 24 '17 at 07:35
  • interesting, I don't use `*ngComponentOutlet` so didn't know they introduced that functionality. the root injector contains many application wide providers like `ApplicationRef`, `ErrorHandler`, `rendererFactory` etc, so you can't avoid it completely - otherwise none of your components will be able to access them. Why do you want your components to not have access to parent injector providers? – Max Koretskyi Jul 24 '17 at 07:47
  • It's not critical to the operation of the program, but I'd like to preserve isolation wherever possible. Since the child module has little to no knowledge of the parent application, I'd like to provide it with a completely clean slate with respect to dependency injection. Since both the parent and child are expected to use injection tokens, there's definitely a possibility for collision. I was hoping there would be some way to obtain an injector that contained all of the global providers and none of the providers introduced by the application's modules, akin to that of the root module itself. – colavitam Jul 24 '17 at 07:50
  • Got it, see [my answer](https://stackoverflow.com/a/45275785/2545680). Let me know if there's something unclear. – Max Koretskyi Jul 24 '17 at 08:34
  • is there anything unclear about my answer? – Max Koretskyi Jul 24 '17 at 16:02
  • Nope; thank you for your response. It does seem like a fairly substantial engineering oversight to not provide access to the root injector with the resources necessary to bootstrap the main module. Asking for a fresh dependency injector seems like a very reasonable request from a framework that relies on it so heavily. – colavitam Jul 24 '17 at 17:10
  • yeah, let me know if in the future you will come across something like that. good luck – Max Koretskyi Jul 24 '17 at 17:11

1 Answers1

1

It's not possible to restrict access to the application injector because it contains application wide global providers like ApplicationRef, ErrorHandler, rendererFactory etc. Also, it's impossible to get only the injector with these providers because all module providers are merged. Read more in this answer.

However, it is possible to restrict access to the providers defined by the parent components. Angular uses different kind of injectors for the components - see this answer. To do that you need to pass into the module factory the application injector instead of the injector you obtain through dependency injector into the component constructor. The only way I know now to get this application injector is like this:

platform.bootstrapModule(AppModule).then((module) => {
  window['rootInjector'] = module.injector;
});

There is also platform injector that you can easily obtain but it's useless for our purposes. Read more in this answer. So assume that you have obtained the application injector you can then pass it to the module factory:

System.import('app/b.module').then((module) => {
  const appInjector = window['rootInjector'];
  const moduleFactory_ = c.compileModuleSync(module.BModule);
  const moduleRef = moduleFactory_.create(appInjector);

Or the way you do it with ngComponentOutlet.

The other possible approach is to get the application wide dependencies and remap them inside the custom injector:

const applicationRef = i.get(ApplicationRef);
const errorHandler = i.get(ErrorHandler);
...

const customInjector = ReflectiveInjector.resolveAndCreate([
   {provide: ApplicationRef, useValue: applicationRef},
   {provide: ErrorHandler, useValue: errorHandler}
   ...
]);

But there's a big likelihood for it to fail some time in the future.

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488