Why are Angular Material styles duplicated in my project?
This article assumes that you are using SCSS with the Angular CLI.
What do these duplicated styles look like?
You can see the .mat-toolbar.mat-primary
style at the top. Then you can see it down below once
more. This time with the same exact styles (background
, color
) and values, but all of them
overridden.
In this example, there is only 1 duplicate set of styles, but it is very easy to suddenly see 3 duplicate sets or 5 or more! As you might imagine, this can significantly impact your bundle size in addition to complicating the styling of your app.
Now show some examples to showcase how easy it is to get into this situation…
From the Angular Material Theming Guide
A typical theme file will look something like this:
styles.scss:
@import '~@angular/material/theming';
// Plus imports for other components in your app.
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat-core();
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue.
$candy-app-primary: mat-palette($mat-indigo);
$candy-app-accent: mat-palette($mat-pink, A200, A100, A400);
// The warn palette is optional (defaults to red).
$candy-app-warn: mat-palette($mat-red);
// Create the theme object (a Sass map containing all of the palettes).
$candy-app-theme: mat-light-theme($candy-app-primary, $candy-app-accent, $candy-app-warn);
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include angular-material-theme($candy-app-theme);
Now if this is all you do, you won’t run into problems, even if you ignore most of the comments.
However, we all know that real production apps need to integrate this code into a much more complex system. For instance, the full Angular Material theme may not be included in the App Shell (used to quickly paint the screen and let users on low bandwidth devices know that your app is bootstrapping).
In this article, we’ll use https://angular.io and its code as an example since many developers look at this AIO repo and copy the code from it into their own apps.
ng-io-theme.scss:
@import '~@angular/material/theming';
// Plus imports for other components in your app.
// Include the base styles for Angular Material core. We include this here so that you only
// have to load a single css file for Angular Material in your app.
@include mat-core();
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue.
$ng-io-primary: mat-palette($mat-blue, 700, 600, 800);
$ng-io-accent: mat-palette($mat-red, 700, 600, 800);
// The warn palette is optional (defaults to red).
$ng-io-warn: mat-palette($mat-red);
// Create the theme object (a Sass map containing all of the palettes).
$ng-io-theme: mat-light-theme($ng-io-primary, $ng-io-accent, $ng-io-warn);
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include angular-material-theme($ng-io-theme);
There’s a very important comment that has been removed from this version.
// *Be sure that you only ever include this mixin once!*
Now if you miss that, and you use some other examples like the following, you may start to see
duplicate styles and even need to resort to !important
on many of your styles that target Angular
Material classes like .mat-form-field
or .mat-raised-button
.
I have run into apps with these issues, and it has resulted in 500 kB to 1.5 MB of bloat in the developer build of the site. In production builds the impact is smaller, but every kB counts when shooting for great WebPageTest or Lighthouse scores!
main.scss:
// import global themes
@import '~@angular/material/theming';
@import './ng-io-theme';
// import global variables
@import './constants';
// import global mixins
@import './mixins';
// import directories
@import './0-base/base-dir';
@import './1-layouts/layouts-dir';
@import './2-modules/modules-dir';
You can see here that main.scss
is including ng-io-theme.scss
which includes @include mat-core();
.
styles.scss:
/* You can add global styles to this file, and also import other style files */
@import './styles/ng-io-theme';
@import './styles/main.scss';
You can see here that styles.scss
is including ng-io-theme.scss
as well. Then including
main.scss
which also includes @include mat-core();
. There is another include here that is
getting duplicated as well, @include angular-material-theme($ng-io-theme);
.
The AIO repo uses considerably more (custom scripts, rollup, etc.) than just the Angular CLI to bundle and deploy their application. I wasn’t able to determine exactly how they avoid duplicate Angular Material styles with this setup, but I did verify that there are not any duplicate Angular Material styles on the production site.
How should I do this in my app?
You probably want to stick closer to the Angular CLI only approach for your own apps as long as you can. So I’ll provide an example to use in that case.
styles.scss:
@import './styles/theme';
// Include the base styles for Angular Material core. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat-core();
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component that you are using.
@include angular-material-theme($app-theme);
html {
...
This is the src/styles.scss
file found in Angular CLI projects with SCSS enabled.
Styles in this file are applied globally without any
ViewEncapsulation
.This file is included in your app only once via the
.angular-cli.json
file via“styles”: [“styles.scss”]
.You should not import this file into any other SCSS file.
The Angular Material
@include
statements should be in this file.
Note: If you are using an App Shell for your app, the Angular Material @include
statements
are probably in another file. Just make sure that it is only included once.
However, this file is free to include other SCSS files like your src/styles/_theme.scss
found below
_theme.scss:
@import '~@angular/material/theming';
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker hue.
$app-primary: mat-palette($mat-teal, 700, 100, 900);
$app-accent: mat-palette($mat-orange);
// The warn palette is optional (defaults to red).
$app-warn: mat-palette($mat-red);
// Create the theme object (a Sass map containing all of the palettes).
$app-theme: mat-light-theme($app-primary, $app-accent, $app-warn);
In this file we define the $app-theme
variable that is later used in src/styles.scss
.
Note: We’re using a feature of SCSS here that is called “Partials”. You can find out more about
them in this StackOverflow answer or in
the official SASS docs. Note that the _
in the filename is very
important here and changes the behavior of SASS.
Now this file can be imported in many places in your app to give you access to variables like
$app-accent
for use in a statement like the following without duplicating styles.
app.component.scss:
@import '../../styles/theme';
.noRecordsContainer {
padding: 48px;
background-color: mat-color($app-accent, 50);
width: 100%;
}
.errorMessageContainer {
padding: 48px;
background-color: mat-color($app-warn, 50);
width: 100%;
}
This lets you use your Material Design palette in your custom components. In this case, we’re using
the lightest accent color (50
) as the background of the container which provides a message about
the empty search result set. We’re also using the lightest accent color of the warn palette as the
background color of our error panel.
These are just two very simple examples of the powerful capabilities of using SCSS along with the Angular Material Theming system. Find our more in their guide to theming your own components.
Summary
Be careful of how you set up SCSS in your Angular Material and Angular CLI project. It can lead to bloat and styling complications.
Follow the architecture above to eliminate duplicate styles if you have them
Brush up on the basics of SASS and the capabilities of the Angular Material Theming system
Keep leveraging these great tools to build amazing high performance web applications with solid UX!
I hope that you find this helpful. If you would like to see more Angular Material content, please let me know.
There will be some great presentations next week (Oct 10–12th) at AngularMix in Orlando, FL USA. I’ll be presenting “Delight your Organization with Material Design” on 10/12. There will be many other talks including Angular’s new Component Dev Kit (CDK), which Angular Material leverages, and Angular CLI’s new DevKit and schematics.
Tickets are still available, and you can get $100 off with the code MIXPRENTICE
! They include an
after party at the Wizarding World of Harry Potter in Universal Studios Orlando. I hope to see you
there!