DEV Community

Cover image for You don't need importProvidersFrom with Angular Material

You don't need importProvidersFrom with Angular Material

Cover photo generated with Microsoft Designer.

Traditionally, we had to import mixed Angular modules from Angular Material at the root level to provide services required for the following components.

Standalone Angular application

In Angular 14.0, the importProvidersFrom function was introduced with standalone application support in Angular. This allowed us to be more explicit about the reason for adding the Angular module import to the ApplicationConfig used with bootstrapApplication as seen in the following example.

import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { MatNativeDateModule, MAT_DATE_FORMATS } from '@angular/material/core';
import { MatDatepickerModule} from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSnackbarModule } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { appDateFormats } from './app-date-formats';

export const appConfig: ApplicationConfig = {
  providers: [
    // Datepicker (and Timepicker)
    importProvidersFrom(MatDatepickerModule, MatNativeDateModule),
    { provide: MAT_DATE_FORMATS, useValue: appDateFormats },
    importProvidersFrom(MatDialogModule),
    importProvidersFrom(MatSnackbarModule),
    importProvidersFrom(MatTooltipModule),
  ],
};
Enter fullscreen mode Exit fullscreen mode
app.config.ts example with Angular Material 14.0.

Classic Angular application

In an AppModule in a classic Angular application, it is less clear why we need to maintain these Angular module imports.

import { ApplicationConfig, importProvidersFrom, NgModule } from '@angular/core';
import { MatNativeDateModule, MAT_DATE_FORMATS } from '@angular/material/core';
import { MatDatepickerModule} from '@angular/material/datepicker';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSnackbarModule } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppComponent } from './app.component';
import { appDateFormats } from './app-date-formats';

@NgModule({
  declarations: [AppComponent],
  bootstrap: [AppComponent],
  imports: [
    // Angular Material
    BrowserAnimationsModule,
    // Datepicker (and Timepicker)
    MatDatepickerModule,
    MatNativeDateModule,
    { provide: MAT_DATE_FORMATS, useValue: appDateFormats },
    MatDialogModule,
    MatSnackbarModule,
    MatTooltipModule,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode
app.module.ts example with Angular Material 13.3.

Standalone Angular Material providers

In recent Angular Material versions, we can leave out the following imports from our ApplicationConfig as the dependencies they provide are all tree-shakable (@Injectable({ providedIn: 'root' }) or InjectionToken with an inline provider).

As for the Datepicker and Timepicker dependencies, we can use the provideNativeDateAdapter function or a date-time library-specific date adapter like provideDateFnsAdapter as seen in the following example.

import { ApplicationConfig } from '@angular/core';
import { provideNativeDateAdapter } from '@angular/material/core';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { appDateFormats } from './app-date-formats';

export const appConfig: ApplicationConfig = {
  providers: [
    provideAnimations(),
    // Datepicker (and Timepicker)
    provideNativeDateAdapter(appDateFormats),
  ],
};
Enter fullscreen mode Exit fullscreen mode
app.config.ts example with Angular Material 17.1.

Much cleaner, right?

Benefits

Other than simpler application configuration code that is easier to reason about, we get the following benefits.

  • Smaller bundle size as the Angular Material service dependencies are loaded when a lazy-loaded chunk need them for the first time
  • Simpler component story configuration in Storybook as they can be simplified simlar to our ApplicationConfig as seen in the next section
  • Simpler component test configuration as it is simplified in a similar way as seen in a later section

Storybook component stories

In component stories, we use the applicationConfig Storybook decorator to add root-level providers.

import { provideNativeDateAdapter } from '@angular/material/core';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { Meta, StoryObj, applicationConfig } from '@storybook/angular';
import { myDateFormats } from './my-date-formats';
import { MyMaterialComponent } from './my-material.component';

const meta: Meta<MyMaterialComponent> = {
  title: 'MyMaterialComponent',
  component: MyMaterialComponent,
  decorators: [
    applicationConfig({
      providers: [
        // Angular Material
        provideAnimationsAsync(),
        // Datepicker (and Timepicker)
        provideNativeDateAdapter(myDateFormats),
      ],
    }),
  ],
};

export default meta;
type Story = StoryObj<MyMaterialComponent>;

export const Default: Story = {};
Enter fullscreen mode Exit fullscreen mode
my-material.component.stories.ts example with Angular Material 17.1.

In earlier versions of Angular Material, we had to use importProvidersFrom or pass the Angular modules to the imports option of thte moduleMetadata Storybook decorator.

Component tests

Angular Material depdendencies are also simpler to configure in component tests. When unit testing an Angular component, we configure root-level providers with the providers option for the TestBed.configureTestingModule method.

import { TestBed } from '@angular/core/testing';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
import { myDateFormats } from './my-date-formats';
import { MyMaterialComponent } from './my-material.component';

it('MyMaterialComponent', () => {
  TestBed.configureTestingModule({
    providers: [
      // Angular Material
      provideNoopAnimations(),
      // Datepicker (and Timepicker)
      provideNativeDateAdapter(myDateFormats),
    ],
  });

  const fixture = TestBed.createComponent(MyMaterialComponent);

  expect(fixture.componentInstance).toBeDefined();
});
Enter fullscreen mode Exit fullscreen mode
my-material.component.spec.ts example with Angular Material 17.1.

Conclusion

Modern Angular Material versions are easier to use, in part because of the standalone providers used for component dependendencies since version 17.1. The following table shows the exact versions when each Angular Material component was converted to standalone providers.

Component First version with standalone providers
Datepicker 17.1.0
Dialog 17.0.0
Snackbar 17.0.0
Timepicker 19.0.0
Tooltip 15.0.4

Root-level provider configuration for Angular Material components is easier to set up and reason about both in Angular applications, Storybook component stories, and Angular component tests.

Bundle sizes are optimized because of the tree-shakable providers.

Except for Date picker and Timepicker components, we don't have to remember to add any providers. Similarly, we don't have to remember to remove them if and when we remove all Angular Material component uses in an application, a component story, or a component test.

Historically, Angular and Angular Material progressed in the following way.

  1. Add Angular modules to NgModule.imports for AppModule
  2. Pass Angular modules to importProvidersFrom in ApplicationConfig.providers
  3. No Angular modules but a provider function for the Datepicker and Timerpicker dependencies

Top comments (0)