How to use .forRoot() within feature modules hierarchy
Asked Answered
E

3

63

Can anyone please clarify to me how should I structure multiple nested feature modules hierarchy with .forRoot() calls?

For example what if I have modules like this:

- MainModule
- SharedModule
- FeatureModuleA
    - FeatureModuleA1
    - FeatureModuleA2
- FeatureModuleB

All feature modules has a .forRoot() static function.

How should I define FeatureModuleA with somehow "transfer" the .forRoot() functions?

@NgModule({ 
  imports: [
    //- I can use .forRoot() calls here but this module not the root module
    //- I don't need to import sub-modules here, FeatureA only a wrapper
    //FeatureModuleA1.forRoot(), //WRONG!
    //FeatureModuleA2.forRoot(), //WRONG!
  ],
  exports: [
    //I cannot use .forRoot() calls here
    FeatureModuleA1, 
    FeatureModuleA2 
  ]
})
class FeatureModuleA {
  static forRoot(): ModuleWithProviders {
    return {
      //At this point I can set any other class than FeatureModuleA for root
      //So lets create a FeatureRootModuleA class: see below!
      ngModule: FeatureModuleA //should be: FeatureRootModuleA 
    };
  }
}

I can create another class for root usage then set it within the forRoot() function of FeatureModuleA:

@NgModule({
  imports: [
    //Still don't need any sub module within this feature module
  ]
  exports: [
    //Still cannot use .forRoot() calls but still need to export them for root module too:
    FeatureModuleA1, 
    FeatureModuleA2 
  ]
})
class FeatureRootModuleA { }

But how can I "transfer" .forRoot() calls within this special ModuleClass?

As I see I need to import all sub-modules directly into my root MainModule and call .forRoot() for each there:

@NgModule({
  imports: [
    FeatureModuleA1.forRoot(),
    FeatureModuleA2.forRoot(),
    FeatureModuleA.forRoot(),
    SharedModule.forRoot()
  ]
})
class MainModule { }

Am I right? Before you answer please take a look at this file: https://github.com/angular/material2/blob/master/src/lib/module.ts

As I know this repo is maintained by the official angular team. So they solve the above by just importing all .forRoot() calls within a special MaterialRootModule module. I don't really understand how it would be applied for my own root module? What does the root and .forRoot really mean here? Is that relative to the package and not to the actual web project?

Ezmeralda answered 23/9, 2016 at 5:17 Comment(4)
I am not sure why you want to create and use a forRoot method.. maybe this helps: angular.io/docs/ts/latest/guide/ngmodule.htmlFeeley
Yes. I read the official documentation. There is nothing there about how to strucuture complex projects with multiple nested modules. I want to create and use forRoot exactly because provider singletons should be instatiated only once within the whole project.Ezmeralda
Ok, cause there were no providers in your example. :)Feeley
It was just an example focusing on the real issue. BTW in the official docs they mention to create a module just to encapsulate others - without need any provider at all.Ezmeralda
H
112

Generally forRoot is used to add application/singleton services.

@NgModule({
  providers: [ /* DONT ADD HERE */ ]
})
class SharedModule {
  static forRoot() {
    return {
      ngModule: SharedModule,
      providers: [ AuthService ]
    }
  }
}

The reasoning is that if you add the AuthService to the providers in the @NgModule, it's possible for more than one to be created if you import the SharedModule into other modules.

I'm not 100% clear on whether the service would be created when the SharedModule is imported into an eagerly loaded module, but the explanation that the docs mentioned was in regards to lazily loaded modules. When you lazily load a module, all the providers will get created.

For this reason, we add a (by convention) forRoot method to signify that the method should only be called for the root (app) module, while for other module it should just be imported normally

@NgModule({
  imports: [SharedModule]
})
class FeatureModule {}

@NgModule({
  imports: [SharedModule.forRoot()]
})
class AppModule {}
Harriette answered 23/9, 2016 at 6:17 Comment(13)
Thank you for your efforts but this still don't care about multi-level nested module case. In your example you imported AuthService into the SharedModule. Can you please refactor your example with importing SharedModule (module, instead of service!) - and try to use AuthModule.forRoot() somewhere. (disclaimer: for me the AuthService/AuthModule just an example here - it can be any module with forChild and forRoot methods.)Ezmeralda
forRoot should only be used for the app module. There should not be a need to forward anything. When you import Just SharedModule without forRoot, forRoot is never called. We should structure our modules this way. App-wide services in forRoot, and everything else in the NgModule. Nested or not, this is how you should consider itHarriette
If you need to import the feature module into other feature modules, go ahead, it doesn't hurt. Just remember the rule of thumb about app-wide providers in the forRoot and you should be goodHarriette
Also remember that declaration are not inherited by modules. So you will still need to import a feature module into other modules if it needs its declarations.Harriette
Can you please update your example to demonstrate this concept. I think I understand every aspect of modules and forRoot but still don't know how to transfer app-wide providers through the hierarchy.Ezmeralda
Any module with app-wide providers, add a forRoot like in my SharedModule example, and import it into AppModule calling forRoot. Does my answer not convey that? If not I will try to update it to make it more clear. Other than that, maybe I'm not understanding what "transfer app-wide providers through the hierarchy" means. From what I understand from that statement, what I've explained handles that.Harriette
If your main objective is to try and figure out how you can call forRoot in a feature module and just have it somehow transfer to app module, I dont think that's gonna happenHarriette
Yes! I try to figure out exactly that! Did you check the link of the material repo? They just imported with .forRoot() within a "feature" module - and also created a special MaterialRootModule to handle the case. BTW there is no any information in the official docs about this technique. Finally they solved for me with that I no need to import and call forRoot() for each sub-modules. So I try to solve somehow same issue inside my own project.Ezmeralda
It's because that module is strictly meant to be used with the app module. It's not any type of feature module. It just looks like a helper module if you want to add all the modules to your app without having to import them manually. But if you try to add that module to a bunch of different modules, you run the risk of having a bunch a different instances of providers that should only be singletonsHarriette
Fine. I think I understand. Only one question left: providers from those sub-modules marked with a comment '//These modules include providers.' will be available for the whole project? Or that providers only accessible within the MaterialModule itself?Ezmeralda
If you import the Root Module into the App module, yes they are available app-wide. But that's only providers. Component, directives, and pipes, you still need to import the "Non root" module into whatever module you need it in. It's weird, but that's just how it is. You could export from your ShareModule so that you only need to import your SharedModule.Harriette
So you say if I import a module into my AppModule which imports another modules with forRoot() suffix all of those providers will be available for the whole project without the need of re-importing all subs with forRoot() inside the AppModule. Is it true?Ezmeralda
yea but you should only import a forRoot in a situation like with material where they made a consolidator of root modules. If that sub-module is meant to be use with other modules, then you should not be importing a forRoot module into that sub-module.Harriette
C
12

As forRoot intended only to provide singleton services, you can "re-provide" them explicitly in SharedModule:

@NgModule({})
class SharedModule {
  static forRoot() {
    return {
      ngModule: SharedModule,
      providers: [
        AuthService,
        FeatureModuleA.forRoot().providers,
        FeatureModuleB.forRoot().providers,
      ],
    }
  }
}

This way all the services will be provided by the SharedModule itself (not by respective sub-module) but it seems that it doesn't matter. Maybe someone can argue though...

Note, that FeatureModuleA can also "re-provide" singleton services from its sub-modules it similar manner.

Cherise answered 5/4, 2018 at 10:7 Comment(2)
what is missing in most answers is - how then do you use AuthService in other modules - if I don't put AuthService in providers it fails. If I put it in providers it doesn't treat it as a singleton.Ulric
You should add SharedModule.forRoot() to the "imports" of your AppModule. This way Angular will provide all that services for root injector (AppModule), hence singletons.Cherise
A
1

As mentioned above, lazy loaded modules are important to consider because a shared service could be used in a lazy loaded module, but if that service was already used and instantiated by another module, then there will be two instances hence the need for the singleton pattern. An AuthService is a great example here - you don't want to have one instance of the service with an unauthenticated user while another has "the same" user authenticated.

New to Angular 6 there is a new way to register a provider as a singleton. Inside the @Injectable() decorator for a service, use the providedIn attribute. Set its value to 'root'. Then you won't need to add it to the providers list of the root module, or in this case you could also set it to your SharedModule like this:

@Injectable({
  providedIn: SharedModule // or 'root' for singleton
})
export class AuthService {
...
Addle answered 8/6, 2018 at 14:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.