Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 How to make singleton service available to lazy loaded modules

According to my understanding of Angular 2 rc5, to make a service from another module (not AppModule) available as a singleton to every component, even those lazy-loaded, we don't include the service in the providers array of that other module. We instead export it with RouterModule.forRoot() and import the result in AppModule

According to the docs:

The SharedModule should only provide the UserService when imported by the root AppModule. The SharedModule.forRoot method helps us meet this challenge...the SharedModule does not have providers...When we add the SharedModule to the imports of the AppModule, we call forRoot. In doing so, the AppModule gains the exported classes and the SharedModule delivers the singleton UserService provider at the same time

I'm really struggling with how to make a 3rd-party service (a service used by a module in the imports array of my AppModule) available to lazy loaded routes. I have no control over this 3rd-party module, so I cannot just remove that service from the NgModule.providers array of that module and place it inside RouterModule.forRoot() as I would with one of my services.

The specific service is MdIconRegistry, which is in providers for the MdIconModule of Angular Material 2 alpha 7-3. This service is used to register svg icons that can then be displayed on the page with the <md-icon svgIcon='iconName'> tag. So:

  • I imported MdIconModule in my root AppModule
  • I used the service in question to register svg icons in my AppComponent

The icon is visible and works well, but only in the modules that were loaded at launch. Lazy-loaded modules cannot see these icons, so I suspect that the Angular injector is not injecting the same instance of the MdIconRegistry service.

tl;dr: How can I make the service from a 3rd-party module a singleton available to my lazy-loaded components?

Here is a plunker that demonstrates the problem (coded in typescript).

PS: This just got the attention of the MdIconModule developer on github.

like image 791
BeetleJuice Avatar asked Aug 18 '16 05:08

BeetleJuice


People also ask

How do you provide a singleton service in Angular?

There are two ways to make a service a singleton in Angular: Set the providedIn property of the @Injectable() to "root" Include the service in the AppModule or in a module that is only imported by the AppModule.

Why is it bad if Sharedmodule provides a service to a lazy loaded module?

Why is it bad if a shared module provides a service to a lazy-loaded module? The lazy loaded scenario causes your app to create a new instance every time, instead of using the singleton. Lazy loading is the best practice of loading expensive resources on-demand.

Are Angular services singletons by default?

The answer would be no. The main objective of angular services is to share data across Angular application. Practically an angular service can be shared between all the components or can be limited to some component. Hence Angular service can be a singleton as well as non-singleton in nature.

How are modules loaded lazily in Angular?

To lazy load Angular modules, use loadChildren (instead of component ) in your AppRoutingModule routes configuration as follows. content_copy const routes: Routes = [ { path: 'items', loadChildren: () => import('./items/items.module').then(m => m.ItemsModule) } ];


1 Answers

I do not think it has anything to do with the component being lazy-loaded.

LazyLoadedComponent is not part of the AppModule – it is part of the LazyModule. According to the docs, a component can only be part of one module. If you try adding LazyLoadedComponent to AppModule also, you would get an error to that effect. So LazyLoadedComponent is not even seeing MdIconModule at all. You can confirm this by looking at the template output in the debugger – it is unchanged.

<md-icon svgIcon="play"></md-icon>

The solution appears to be adding the MdIconModule to the LazyModule, and while this alone does not fix the problem, it does add an error to the output.

Error retrieving icon: Error: Unable to find icon with the name ":play"

And the template output now looks like this, so we know it is loading.

<md-icon role="img" svgicon="play" ng-reflect-svg-icon="play" aria-label="play"></md-icon>

I added the call to addSvgIconSet from LazyLoadedComponent, and that got it working… so this proves there is an instance of the MdIconRegistry service per component – not what you want, but may point you in the right direction.

Here’s the new plunk - http://plnkr.co/edit/YDyJYu?p=preview

After further review, I found this in the docs:

Why is a service provided in a lazy loaded module visible only to that module?

Unlike providers of the modules loaded at launch, providers of lazy loaded modules are module-scoped.

Final Update! Here is the answer. MdIconModule is not properly setup for lazy loaded components... but we can easily create our own module that IS properly set up and use that instead.

import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';

import { MdIcon } from '@angular2-material/icon';
import { MdIconRegistry } from '@angular2-material/icon';

@NgModule({
  imports: [HttpModule],
  exports: [MdIcon],
  declarations: [MdIcon]
})
export class MdIconModuleWithProviders {
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: MdIconModuleWithProviders,
      providers: [ MdIconRegistry ]
    };
  }
}

Plunk updated and fully working. (sorry, updated the same one) -> http://plnkr.co/edit/YDyJYu?p=preview

One might submit a pull request such that Angular Material exports modules of both styles.

like image 69
James Avatar answered Oct 05 '22 01:10

James