Creating a Custom NgModule for Material Components inย Angular

Angular Material provides material design component for Angular apps.

In earlier versions of Angular Material, we could import MaterialModule which imports all components from Angular Material and makes them available in our app. Most of the time we will need to import BrowserAnimationsModule too, as many Material components depend on animations.

// app.module.ts
...
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MaterialModule } from '@angular/material';
...
@NgModule({
...
imports: [
BrowserModule,
BrowserAnimationsModule,
MaterialModule
],
...
export class AppModule { }

We can now use Material components in our app. Here for example, I use Input and Button components.

MaterialModule was introduced in alpha.9, and still works until at least beta.8. However, it has been marked as deprecated since beta.3. It was also removed from the official getting started guide.

The issue is that MaterialModule makes it currently not possible for tools to remove unused modules/components in tree-shaking process. The output bundle file from ng build will still include all material components even we donโ€™t use some of them.

Fortunately, Angular Material also provides modules for each component so our app can import only ones that we use. From the example above, instead of importing the whole MaterialModule, we can import only MatInputModule and MatButtonModule into our app.module.ts.

...
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatInputModule, MatButtonModule } from '@angular/material';
...
@NgModule({
...
imports: [
BrowserModule,
BrowserAnimationsModule,
MatInputModule,
MatButtonModule
],
...
export class AppModule { }
*Outdated screenshot*: Module name can be found at API tab for each components in theย docs.

It looks OK for a few Material component modules. But practically our app needs more than that to create a fully function application. We might end up with importing 10 or 20 Material modules to use in many of our components. Results in AppModule is cluttered with a lengthy list of imported modules.

To keep AppModule clean, we can create another NgModule that takes care of importing Material modules to use. Same idea with moving routing logic to a routing module.

Creating a custom materialย module

We can create a new NgModule with ng generate module command.

$ ng generate module app-material

Then we import Material component modules in this new AppMaterialModule. Note that we also need to put the same list of modules in NgModule.exports too.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatInputModule, MatButtonModule } from '@angular/material';
@NgModule({
imports: [
CommonModule,
MatInputModule,
MatButtonModule
],
exports: [
MatInputModule,
MatButtonModule

]
})
export class AppMaterialModule { }

Then in our AppModule, we can import on AppMaterialModule.

import { AppMaterialModule } from './app-material/app-material.module';
...
@NgModule({
...
imports: [
BrowserModule,
BrowserAnimationsModule,
AppMaterialModule
],
...
export class AppModule { }

Later on if the app needs more Material components, we only need to import new component modules in AppMaterialModule while AppModule remains the same.

import {
MatInputModule,
MatButtonModule,
MatSelectModule,
MatToolbarModule
} from '@angular/material';
@NgModule({
imports: [
CommonModule,
MatInputModule,
MatButtonModule,
MatSelectModule,
MatToolbarModule

],
exports: [
MatInputModule,
MatButtonModule,
MatSelectModule,
MatToolbarModule

]
})
export class AppMaterialModule { }

To summarize:

  • We move taking care of importing Material component modules into an isolated module: AppMaterialModule.
  • We keep AppModule clean, less cluttered.
  • We use future-proof syntax to import Material component modules. This will still work when MaterialModule is removed.

However, there seems to be still an open bug in webpack that tree shaking still keeps unused classes in the bundle file. Since Angular CLI uses webpack, this is buggy for the CLI too (see angular-cli/2901). There is also an issue on Angular Material repo. With this, we get always every Material components module in the bundle file. Hopefully this gets fixed soon.

Results from using source-map-explorer with bundle files from the example in thisย post.