Angular 4 - services provided in core of one module for real?

I'm trying to understand the core singleton modules and services in angular 4. The official documentation ( https://angular.io/guide/ngmodule ) says this:

UserService is a singleton application scope. You don't want everyone to have their own individual copy. However, there is a real danger of what happens if the SharedModule exposes a UserService.

CoreModule provides UserService. angular registers this provider with the application root injector, creating a singleton UserService instance available to any component that needs it, whether the component is eager or lazy loaded.

We recommend that you collect such one-off classes and hide their details inside the CoreModule. Simplified root import of AppModule CoreModule as developer orchestra as a whole.

import { CommonModule }      from '@angular/common';
import { TitleComponent }    from './title.component';
import { UserService }       from './user.service';
import { ModuleWithProviders, NgModule, Optional, SkipSelf }       from '@angular/core';
@NgModule({
  imports:      [ CommonModule ],
  declarations: [ TitleComponent ],
  exports:      [ TitleComponent ],
  providers:    [ UserService ]
})
export class CoreModule {
    constructor (@Optional() @SkipSelf() parentModule: CoreModule) { ... }
}

      

So I am using Core Module providing singleton services and the constructor

constructor (@Optional() @SkipSelf() parentModule: CoreModule) { ... }

      

prevent the main module from being imported more than once.

1) BUT, what if I expose the UserService in another module (like a lazy loading module)? Does this lazy module have a new service instance?

And about the forRoot method:

@NgModule({
  imports:      [ CommonModule ],
  providers:    [ UserService ]
})
export class CoreModule {
}

static forRoot(config: UserServiceConfig): ModuleWithProviders {
  return {
    ngModule: CoreModule,
    providers: [
      {provide: UserServiceConfig, useValue: config }
    ]
  };
}
}

      

2) If I import CoreModule using CoreModule.forRoot () in AppModule, what happens to UserService? Is this also provided?

thank

+3


source to share


3 answers


The documentation is confusing, especially this line:

UserService is a singleton application scope. You don't want everyone to have their own individual copy. However, there is a real danger of what happens if the SharedModule exposes a UserService .

There is no danger of this if you are not using lazy loadable modules. Let's see an example. You have a module A

that is importing a module B

. Both modules define suppliers:

@NgModule({
   providers: {provide: 'b', 'b'}
})
export class BModule {}

@NgModule({
   imports: [AModule]
   providers: {provide: 'a', 'a'}
})
export class AModule {}

      

What happens when the compiler creates a factory module is that it bundles these providers and the factory together for just one module . This is how it will look:

var AModuleNgFactory = jit_createNgModuleFactory0(

    // reference to the module class
    jit_AppModule1,  

    // array of bootstrap components
    [jit_AppComponent2],

    function (_l) {
        return jit_moduleDef3([

            // array of providers
            jit_moduleProvideDef4(256, 'b', 'b', []),
            jit_moduleProvideDef4(256, 'a', 'a', [])
            ...,
        ]);

      

You can see that the suppliers are merged. Now, if you define two modules with the same provider token , the modules will be merged, and the providers from the module that imports the other will override the providers of the imported modules:

@NgModule({
   providers: {provide: 'a', 'b'}
})
export class BModule {}

@NgModule({
   imports: [AModule]
   providers: {provide: 'a', 'a'}
})
export class AModule {}

      



The factory definition now looks like this:

function (_l) {
    return jit_moduleDef3([

        // array of providers
        jit_moduleProvideDef4(256, 'a', 'a', []),
        ...,
    ]);

      

This way, no matter how many modules you import, only one factory with pooled providers is created. And only one root injector is created. The injector that creates the components is not a "real" injector - check this answer to see why.

Does this lazy module have a new service instance?

When it comes to lazy loadable modules, Angular creates separate ones for them . This means that the providers defined in them are not bundled into the main injector of the module. Therefore, if a lazy loaded module defines a provider with the same token, Angular will create a new instance of that service, even if there is already one in the injector of the main module.

If I import CoreModule using CoreModule.forRoot () in AppModule

To understand what it does forRoot

, see RouterModule.forRoot (ROUTES) vs RouterModule.forChild (ROUTES) .

+3


source


1) Yes. This is because the dependency injector is hierarchical .

This means that each module has a set of elements that can be injected (module level), and if one of its elements requires a dependency that is not present at the module level, then the dependency injector will look for dependencies in the parent module (one module that is imported) etc. until it finds the dependency or reaches the root (app.module) where it will throw an error if the dependency conflict is not resolved (hierarchy level).

2) Yes, it will be provided UserService

. forRoot

will create another "version" CoreModule

in which the "normal" CoreModule is extended with additional added properties.

In most cases, forRoot

will accept the "normal" version of the module and include an array of providers to ensure that the services are solid. The "normal" version of the module will only have components, pipes, or other non-selective elements.



Take TranslateModule

ngx-translate (extracted relevant section) as an example :

@NgModule({
    declarations: [
        TranslatePipe,
        TranslateDirective
    ],
    exports: [
        TranslatePipe,
        TranslateDirective
    ]
}) // this defines the normal version of the module
export class TranslateModule {
    static forRoot(): ModuleWithProviders { // this kinda tells "module + providers"
        return {
            ngModule: TranslateModule, // take the normal version
            providers: [ // merge this to the providers array of the normal version
                TranslateStore,
                TranslateService
            ]
        };
    }
}

      

Perhaps this resource could be helpful as an additional explanation: https://www.youtube.com/watch?v=8VLYjt81-fE

+1


source


Let me try to summarize what I think to find out:

@NgModule({
  imports:      [ CommonModule ],
  providers:    [ 
    UserService,
    UserServiceConfig
    ]
})
export class CoreModule {
}

static forRoot(config: UserServiceConfig): ModuleWithProviders {
  return {
    ngModule: CoreModule,
    providers: [
      {provide: UserServiceConfig, useValue: config }
    ]
  };
}
}

      

When I import the module using the forRoot method:

  • UserService is also provided as (as you guys explained) forRoot creates an extended version of this module (it bundles services)
  • UserServiceConfig is provided using the config argument of the forRoot method.

And regarding a singleton application:

In order to have sibling services (even for lazy modules) that can be used in an application, I can use the forRoot method:

static forRoot(): ModuleWithProviders {
  return {
    ngModule: MyModule,
    providers: [
      MySingletonService
    ]
  };

      

  • forRoot only needs to be called once in the appModule (so that's why the method is called "forRoot" by convention)
  • If a module is imported in multiple modules, MySingletonService is not provided (as it is only provided through the forRoot method)

BUT

If I create a CoreModule with the following custom constructor it prevents the module from loading more than once, so the services provided are generic one-liners:

constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
  if (parentModule) {
    throw new Error(
      'CoreModule is already loaded. Import it in the AppModule only');
  }
}

      

So it makes sense to use the forRoot method in the SharedModule rather than in the module with the custom constructor above.

So, if I need services (even lazy modules) for the entire application, I see two options:

  • Common module with forRoot method called in appModule
  • A main module with a custom constructor and services provided normally, without a forRoot method.

Any comments?

0


source







All Articles