Cover photo generated with Microsoft Designer.
In the past, developers had to include mixed Angular modules from Angular Material at the app's root level to supply the services that these components depend on.
Standalone Angular application
With the arrival of Angular 14.0, the importProvidersFrom function emerged, bringing support for standalone applications. This approach made the purpose of integrating the Angular module into the ApplicationConfig used alongside bootstrapApplication much clearer, as illustrated in the example below.
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),
],
};
app.config.ts example with Angular Material 14.0.Classic Angular application
In a traditional Angular setup, the purpose behind keeping these module imports inside an AppModule isn't as obvious.
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 {}
app.module.ts example with Angular Material 13.3.Standalone Angular Material providers
Newer releases of Angular Material let you drop these from your ApplicationConfig entirely, since everything they bring to the table is tree-shakeable — either via @Injectable({ providedIn: 'root' }) or through an InjectionToken that comes with its own inline provider.
When it comes to what the Datepicker and Timepicker need, you can turn to something like provideNativeDateAdapter or a date adapter specific to your date-time library of choice, such as provideDateFnsAdapter, as the example below illustrates.
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),
],
};
app.config.ts example with Angular Material 17.1.That reads a lot more cleanly, doesn't it?
Benefits
Beyond making the application setup code more concise and straightforward to follow, here is what we gain.
- A leaner bundle, since Angular Material’s service dependencies only load when a lazy-loaded chunk first requests them
- Cleaner Storybook component story definitions, which can be simplified to match our
ApplicationConfig, as shown in the upcoming section - Easier component test configuration, streamlined in the same manner demonstrated further below
Storybook component stories
For component stories, the applicationConfig Storybook decorator brings in 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 = {};
my-material.component.stories.ts example with Angular Material 17.1.Angular Material used to require a workaround: the importProvidersFrom helper, or listing the Angular module references in the imports array of the moduleMetadata Storybook decorator.
Component tests
Handling those dependencies in component tests is easier these days as well. When a component is tested in isolation, the root-level providers are set up through the providers configuration passed to the TestBed.configureTestingModule call.
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();
});
my-material.component.spec.ts example with Angular Material 17.1.Conclusion
Using Angular Material has become more straightforward in recent releases, largely thanks to the switch to standalone providers for component dependencies that began with version 17.1. Below, the table lists the precise version in which every Angular Material component adopted these 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 |
Configuring Angular Material components at the root provider level is more straightforward and simpler to understand, whether you're working with Angular applications, Storybook component stories, or Angular component tests.
The providers are tree-shakable, which keeps bundle sizes lean.
With the exception of the Date picker and Timepicker components, there's no need to remember to add any providers. And if we drop all Angular Material component usage from an app, story, or test, there's no need to remember to remove them either.
Angular and Angular Material have evolved through these stages over time.
- Including Angular modules in
NgModule.importsforAppModule - Feeding Angular modules to
importProvidersFromwithinApplicationConfig.providers - No Angular modules required—just a provider function for the Datepicker and Timerpicker dependencies
