3

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:

Jack Hudzenko
  • 157
  • 3
  • 14

1 Answers1

4

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!

overthesanity
  • 1,054
  • 1
  • 8
  • 12
  • Thanks for such a big and great answer! I already tried what you described, but when I call createInjector for imported module - I have an error `Error: Can't resolve all parameters for...` for any providers and declarations inside imported module. Should I enable IVY for this feature to work? `"angularCompilerOptions": { "enableIvy": true},` – Jack Hudzenko Jul 24 '19 at 14:38
  • Sure, you should, this only works with Ivy, you just mentioned in your question that you're using Ivy :D – overthesanity Jul 24 '19 at 14:39
  • Got it, thanks a lot one more time. For now we are just preparing for moving to IVY. So once we will use that, I will be able to use the solution :) – Jack Hudzenko Jul 24 '19 at 14:41
  • @JackHudzenko probably... – overthesanity Aug 13 '19 at 08:04