1

Let's take scenario:

App.module
   |__Lazy1.module
   |    \__LazyService1.module
   |__Lazy2.module
   |    \__LazyService1.module
   \__(many more)

Goal is to have LazyService1.module provide singleton instance for Lazy1/2.module, but only be needed to be loaded when Lazy1/Lazy2 is fetched.

Angular creates and resolves root DI context only once. Any subsequent context (for lazy modules) are child contexts. My problem is that I don't want to provide services (LazyService1.module) in App.module (it's a big module, and only used by 2 lazy loaded ones), so logically my LazyService1.module will NOT be resolved in root DI context.

I need to somehow share DI context between 2 lazy loaded modules, without having to make it root dependency (in App.module).

Is there a way to define shared DI? Can one module access DI context of other?

Can lazy-loaded service be provided in root for other lazy-loaded modules to use, without providing it from root App.module?

As for stack - I don't think I can provide a stack since I have no idea how it would look. I can implement one that eagerly provides service in root, but that is not the question here.

EDIT 2:

App.module:

@NgModule({
    declarations: [
        AppComponent,
    ],
    imports: [
        // Used by all:
        BrowserModule,
        BrowserAnimationsModule,
        CommonModule,
        HttpClientModule,
        // App router:
        AppRoutingModule
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

Lazy-load routing module:

const routes: Routes = [
    { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) },
    { path: 'main', loadChildren: () => import('./main/main.module').then(m => m.MainModule) },
    ... many others, lazy or not
];

Service Module:

@NgModule({
    providers: [
        AuthenticationService
    ],
    declarations: [...],
    exports: [...],
    imports: [
        CommonModule,
    ]
})
export class SecurityModule { }

Service:

@Injectable()
export class AuthenticationService { ... }

And then we have those 2 lazy ones: admin and main.

If I were to import SecurityModule like this:

@NgModule({
    imports: [
        SecurityModule
    ]
})
export class MainModule { }

I will end up with separate AuthenticationService for both Main and Admin. If I import it in App.module, sure it works, but i have to load HUGE SecurityModule, when it's only needed in Main/Admin which might never be accessed.

Shashank Vivek
  • 16,888
  • 8
  • 62
  • 104
Ernio
  • 948
  • 10
  • 25
  • I dont think you need to import `ServiceModule` in `AppModule` , if it will only be used by `Lazy1Module`. Can you create an example on stackbliktz.com ? – Shashank Vivek Jan 03 '20 at 05:01
  • @ShashankVivek Rewritten whole question to point more to DI contexts in lazy scenarios. – Ernio Jan 03 '20 at 05:23

2 Answers2

1

You cannot share service between modules, that will kill the whole purpose of making modules. But for the scenario you can try this approach

App.module
   |__Lazy1.module  
   |                (import)<= SharedModule -> LazyService1.module
   |__Lazy2.module  
   |
   \__(many more)

The above explain create a new module as shared module and create service you want to share between the services and import the module to other modules where you need the service.

Updated:

You need to create a static method called forRoot that exports the service along with the module itself. That will give you a single instance for all the imports of shared module.

Check out this link, I'm 100% sure it answers your question. Happy coding :)

Shared Dependency Tree

I hope this helps, any doubts with this let me know

Nandan
  • 172
  • 8
  • I've tried this earlier and ended up with 2 different instances for each Lazy1/2.module. Did I do something wrong and should revisit (are You certain one instance can be shared between the two?) – Ernio Jan 03 '20 at 05:57
  • Added some code in original question - does Your idea cover my problem? I can't even think of a way to solve this - I might be missing some crucial design fact, so please explain if You can. :) – Ernio Jan 03 '20 at 06:29
  • Updated my answer please check it and up vote if it works :) – Nandan Jan 03 '20 at 10:45
1

So far all the answers were a pretty cool read (even through I actually read hefty of those before posting the question).

The problem with forRoot (and i think all of proposed links) is that (for me) it's not a real DI as it strongly couples App.module with injected module/service - which is the whole issue I've been describing in the question.

Since I am head-strong Java coder (and We do love our dependency injection) I couldn't shake the idea that there must be a way to make it true DI.

So I am pretty sure Shashank Vivek was at least partially wrong here:

because it is the only way to make it work in Angular

The reason it was so hard to find a good solution is because Angular is damn fast-developing framework and pretty much 90% of what you find is at around version 2-4.

Anyway - since Angular 6 we have providedIn in decorator which, while it's been known to me for months (as it was supposedly "true DI" compared to forRoot), it surprised me: It actually is tree-shakeable!

So all in all - You can literally take a service, mark it with providedIn and not touch ANYWHERE (literally no declarations or imports), outside where You actually use it.

Turns out services are creatures of their own and Angular is smart enough to both load and pack them with anything that uses them.

So the answer to my question is:

  1. Remove all and any references to lazy services.
  2. Mark them with @Injectable({ providedIn: 'root' })
  3. Simply inject them into constructor wherever You need them (in lazy-loaded modules).

Angular will make partial lazy modules that contain services (and other stuff) shared between many lazy modules (like in my example) and load them when they are loaded. The instance will be provided in root (in my case) and shared among all app (lazy modules).

And to go even further (since DI is love), here is a nice read on how to take things even further (and I'm talking Spring-level further). :)

https://angular.io/guide/dependency-injection-providers#tree-shakable-providers

https://www.softwarearchitekt.at/aktuelles/the-new-treeshakable-providers-api-in-angular/

I am VERY SURPRISED this was SO HARD to find :O ...buried deep in angular docs.

Here is a demo code to play around with it !

Shashank Vivek
  • 16,888
  • 8
  • 62
  • 104
Ernio
  • 948
  • 10
  • 25