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),
],
};
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 {}
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),
],
};
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 = {};
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();
});
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.
- Add Angular modules to
NgModule.imports
forAppModule
- Pass Angular modules to
importProvidersFrom
inApplicationConfig.providers
- No Angular modules but a provider function for the Datepicker and Timerpicker dependencies
Top comments (0)