The purpose of a NgModule is to declare each thing you create in Angular, and group them together (like Java packages or PHP / C# namespaces).
There is two kind of main structures:
declarations
is for things you’ll use in your templates: mainly components (~ views: the classes displaying data), but also directives and pipes,providers
is for services (~ models: the classes getting and handling data).@NgModule({
declarations: [SomeComponent, SomeDirective, SomePipe],
providers: [SomeService]
})
export class SomeModule {}
Note: since Angular 6, services don’t need to be registered in a module anymore. The use of providers
in a NgModule is now limited to overriding existing services.
The confusion starts with components and services not having the same scope / visibility:
It means the components you declared are only usable in the current module. If you need to use them outside, in other modules, you’ll have to export them:
@NgModule({
declarations: [SomeComponent, SomeDirective, SomePipe],
exports: [SomeComponent, SomeDirective, SomePipe]
})
export class SomeModule {}
On the contrary, services you provided will generally be available / injectable anywhere in your app, in all modules. (exception is for lazy-loaded modules where service is only available for that module. More on this later).
The difference of scope between components and services makes thing messy. So in order to import modules that you need:
@NgModule({
imports: [CommonModule, HttpClientModule, FeatureModule]
})
export class SomeModule {}
you need to know why you import these other modules.
Why? Because given the difference of scope between components and services :
If you fail to understand this, you’ll have errors on components not being available, because you forgot to import their module again.
Or if you import a module for services more than once, it can lead to errors in advanced scenarios like lazy-loading.
Modules to import each time you need them
CommonModule
(all the basics of Angular templating: bindings, *ngIf
, *ngFor
…), except in the first app module, because it’s already part of the BrowserModuleFormsModule
/ ReactiveFormsModule
MatXModule
and other UI modulesModules to import only once
HttpClientModule
BrowserAnimationsModule
or NoopAnimationsModule
CommonModule
is automatically imported when you create a new module.But in real situation, modules can be mixed (containing both components and service), for example, RouterModule
. We will learn a trick to manage these kind of modules later.
But before that
How to set up lazy loaded modules.
// app.module.ts
// ProductModule, OrderModule, UserModule will get loaded
// lazily.
const AppRoutes: Routes = [
{
path: "",
component: IndexComponent,
},
{
path: "products",
loadChildren: "./product/product.module#ProductModule"
},
{
path: "order",
loadChildren: "./order/order.module#OrderModule"
},
{
path: "users",
loadChildren: "./user/user.module#UserModule"
}
];
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
CoreModule,
IndexModule, // <-- this module doesn't get loaded easily
RouterModule.forRoot(AppRoutes) // <-- this one use forRoot
],
bootstrap: [AppComponent]
})
export class AppModule {}
// example product.module.ts, order.module.ts, user.module.ts
//is similar
const ProductRoutes: Routes = [
{
// Here, we’re using an empty path because these will be
// the relative routes for this module, not for the entire
// application
path: "",
component: ProductListComponent,
pathMatch: "full"
// We are using pathMatch: ‘full’ because our route
// is an empty one, and we don’t want it to match any
// other route
},
{
path: "all-products",
component: ProductListComponent
},
{
path: "product/:id",
component: ProductDetailComponent
}
];
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(ProductRoutes), // <-- this one use forChild
SharedModule,
],
declarations: [
ProductComponent,
BestProductComponent,
ProductListComponent,
ProductDetailComponent,
],
exports: [BestProductComponent]
})
export class ProductModule {}
For truth’s sake, we can open the browser inspector and go to the Network tab: you’ll see that once you navigate to the lazy route, a new chunk.js
file will be loaded asynchronously: that’s our LazyModule in action.
Reminder: