Jack Hudzenko
Jack Hudzenko

Reputation: 167

Get injector from module in angular 8

Problem:

I am setting up lazy loading for non-routed module in angular. At version 7 I used NgModuleFactoryLoader and it's function load to lazy load module and get first entry point to the module (service in out case)

this.loader.load('path-to-module')
  .then(factory => {
    const module = factory.create(this._injector);
    return module.injector.get(EntryService);
  });

But in Angular 8 NgModuleFactoryLoader is deprecated so instead I have to load module in that way:

import('path-to-module')
  .then(m => m.MyModule)
  .then(myModule => {
    ...
});

The problem here that I can not retrieve factory and get provider here in a new lazy loading (one of ideas of IVY - no factories).

What I have already tried:

First solution (work only with JIT compiler which is not suits us as I am using AOT compiler for prod)

import('path-to-module')
  .then(m => m.MyModule)
  .then(myModule => {
    return this._compiler.compileModuleAsync(myModule)
      .then(factory => {
        const module = factory.create(this._injector);
        return module.injector.get(EntryService);
      });
});

Second solution (dirty and not fully checked. It is using ngInjectorDef which is new feature of IVY and has no any described API yet):

import('path-to-module')
  .then(m => m.MyModule)
  .then(myModule => {
    const providers = myModule['ngInjectorDef'].providers; // Array<Providers>
    ... find EntryService in providers
});

ngInjectorDef - is a static module class property which is added by angular and has properties factory, providers and imports.

Sources:

Upvotes: 3

Views: 5881

Answers (1)

user10123947
user10123947

Reputation:

I wouldn't say that accessing the ngInjectorDef property is a "dirty hack". Yes, it is not documented anywhere because, as Igor Minar said, Ivy is opt-in preview in Angular 8. But a lot of private functions were already mentioned in some articles of Viktor Savkin, for example directiveInject.

You should not search for your service in the providers property. Imagine that there are 20+ providers and also the EntryService name will be minified in the production to something like t or k.

If you use Ivy - there is a private function called createInjector, that accepts module constructor as an argument.

@Injectable()
export class EntryService {
  public logFromEntryService(): void {
    console.log('Logging from EntryService...');
  }
}

@NgModule({
  providers: [EntryService]
})
export class EntryModule {
  public static getEntryService: () => EntryService = null;

  constructor(injector: Injector) {
    EntryModule.getEntryService = () => injector.get(EntryService);
  }
}

Assume you've got such code, let's use a dynamic import to load this EntryModule:

import { ɵcreateInjector as createInjector } from '@angular/core';

export class AppComponent {
  constructor(private injector: Injector) {
    this.loadEntryModule();
  }

  private async loadEntryModule(): Promise<void> {
    const { EntryModule } = await import('./entry.module');
    createInjector(EntryModule, this.injector);
    EntryModule.getEntryService().logFromEntryService();
  }
}

createInjector is used for instantiating EntryModule, after instanting - the getEntryService static method doesn't equal null.

We could also expose the injector property, like:

public static injector: Injector = null;

constructor(injector: Injector) {
  EntryModule.injector = injector;
}

This might be treated as a service locator, which is kind of anti-pattern. But up to you!

Upvotes: 4

Related Questions