In Angular, every module which is not the AppModule is technically a Feature Module, and it has the following caveats:
CommonModule
instead of BrowserModule
:
While BrowserModule
must be imported in AppModule
(it’s required in order to run the app in the browser), this module must not be imported elsewhere: instead, we must import CommonModule
, which contains Angular’s common directives, such as ngIf
, ngFor
, ngClass
, etc… BrowserModule
also re-exports CommonModule
, so that you can use this directives in AppModule too.AppModule
localhost/contacts/
on, the ContactsModule
will be responsible for its routes.
In addition to that, by having its own routing, modules can be lazily loaded (Gist, Cacher)loadChildren
keyword without actually Lazy Loading the module and define a preloading strategy for lazily loaded modules:Example of a feature module:
import { ProductComponent } from "./product.component";
import { BestProductComponent } from "./best-product/best-product.component";
import { ProductListComponent } from "./product-list/product-list.component";
import { ProductDetailComponent } from "./product-detail/product-detail.component";
import { SharedModule } from "@shared/shared.module";
const ProductRoutes: Routes = [
{
path: "",
component: ProductListComponent,
pathMatch: "full"
},
{
path: "all-products",
component: ProductListComponent
},
{
path: "product/:id",
component: ProductDetailComponent
}
];
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(ProductRoutes),
SharedModule,
],
declarations: [
ProductComponent,
BestProductComponent,
ProductListComponent,
ProductDetailComponent,
],
exports: [BestProductComponent]
})
export class ProductModule {}
Summary:
CoreModule
. If feature module A needs to import service from feature module B consider moving that service into core.CoreModule
and components provided by SharedModule
.CoreModule
and components from SharedModule
. Sometimes this might not be enough to solve particular business case and we will also need some kind of shared feature module which is providing functionality for a limited subset of other feature modules. Like this:The answer to the question “Where should I put all my global services?” would be: AppModule
. This is because services are (in general) app-scoped, which means that they can be accessed from every module.
As briefly described in the Lazy Loading article (Gist, Cacher), every lazy module has its own injector! What that means is that a service provided in a lazy module is only accessible in that module. But it can still access the services previously provided by non-lazy modules (such as AppModule
)!
So technically, global singleton services such as AuthService
or UserService
in AppModule
, since they’ll be available to everyone. However, we really don’t want our AppModule
to be a complete mess… What Angular recommends is to put all of our global services in a separated module, called CoreModule
, and import it ONLY in AppModule
. This way is the same as providing the services in AppModule directly!
In order to prevent inexperienced developers from re importing the CoreModule
we use this little trick: inside our CoreModule
, we can… inject CoreModule! If Angular injects it correctly, it means that a CoreModule
has been already created, and we can throw an error:
@NgModule({
providers: [
// Your services
]
})
export class CoreModule {
constructor(@Optional() @SkipSelf() core: CoreModule) {
if (core) {
throw new Error('CoreModule should only be imported once in App Module');
}
}
}
Summary:
CoreModule
a pure services module with no declarations (no components, pipes, etc)A shared module is the perfect place to declare components, pipes, directives in order to make them reusable: this way, you won’t re-import the same components in every module, you’ll just import the shared module.
Example shared module:
@NgModule({
imports: [
CommonModule,
FormsModule,
HttpClientModule,
RouterModule,
],
declarations: [
NoProductsFoundComponent,
PriceFormatPipe,
ProductCellComponent,
ProductCarouselComponent
],
exports: [
NoProductsFoundComponent,
PriceFormatPipe,
ProductCellComponent,
ProductCarouselComponent
],
providers: [
// services should not be declared here
]
})
export class SharedModule {
// this should be use in AppModule only.
// for lazy loaded modules, we should import SharedModule normally.
static forRoot(): ModuleWithProviders {
return {
ngModule: SharedModule,
providers: [
// If you have a very good reason to have services in shared module,
// use this to declare your services. Otherwise, consider moving the service
// to Core Module.
// For more explanation, See: https://stackoverflow.com/a/46622924/1602807
]
};
}
}
Still remember that:
So how can we import only the component, directive, pipe part of shared module into lazy modules and import the service part to AppModule
?
Angular gives us a special interface we can use to attach services to modules, it’s called ModuleWithProviders
, here it is:
export interface ModuleWithProviders {
ngModule: Type<any>;
providers?: Provider[];
}
So we don’t provide our services in the SharedModule
metadata; instead, we define a static method inside the module, which returns the SharedModule
istelf AND the array of providers just like in the example!
Now in the AppModule
you can import SharedModule.forRoot()
, while in all the other modules you can import SharedModule
(this is exactly how RouterModule
works).
This trick is for the case you absolutely need to have a mixed shared module. To make matters simpler, just try to keep Shared Module free from services.
Summary:
@Input()
and @Output()
decorator).SharedModule
doesn’t have any dependency to the rest of our application.SharedModule
into the specific Feature Modules as needed. You DO NOT import the SharedModule
into your main AppModule
or CoreModule
.For app wide components such as Header and Footer, we can consider putting them in AppModule. If we want to clean up AppModule even more, we can put those in CoreModule.
Only app wide components should be put in AppModule or CoreModule.
For the first page, Index or Home, we should put it in AppModule.