Blog
Contact

Why are Angular Material styles duplicated in my project?

Michael Prentice
Oct 4, 2017

This article assumes that you are using SCSS with the Angular CLI.

Material Design logo

What do these duplicated styles look like?

Duplicate Angular Material Styles

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!

Michael Prentice
Oct 4, 2017