vxh.viet
7/19/2018 - 3:08 AM

Angular Lazy Module and some reminders

Angular Lazy Modules and some reminders

Understanding Angular modules (NgModule) and their scopes

SOURCE

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.

NgModule and scopes / visibility

The confusion starts with components and services not having the same scope / visibility:

  • declarations / components are in local scope (private visibility),
  • providers / services are (generally) in global scope** (public 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).

When to import a NgModule?

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.

  • Is it to use components (or other template-related things, like directives and pipes)?
  • or is is to use services?

Why? Because given the difference of scope between components and services :

  • if the module is imported for components, you’ll need to import it in each module needing them,
  • if the module is imported for services, you’ll need to import it only once, in the first app module.

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.

When to import main Angular modules?

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 BrowserModule
  • FormsModule / ReactiveFormsModule
  • MatXModule and other UI modules
  • any other module giving you components, directives or pipes

Modules to import only once

  • HttpClientModule
  • BrowserAnimationsModule or NoopAnimationsModule
  • any other module providing you services only. That’s why with Angular CLI, 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

A bit on Lazy-loaded modules and some reminders

SOURCE

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:

  • Don’t import LazyModule for any reason! If you do that, you’ll lose all the advantages of lazy-loading and you may have serious problems with debugging: specifying the module’s path is all we need to do to associate it with our route, let Angular do the rest.
  • The services provided in your lazy-loaded module will only be available in this lazy-loaded module, not everywhere in your app.
  • Lazy modules have a strange behavior: they have their own injector, which means if they import a module which provides some services, they’ll create their own instances of those services. Keep this behavior in mind when we need to deal with mixed modules.