Cory Rylan

My name is , Google Developer Expert, Speaker, Software Developer. Building Design Systems and Web Components.

Follow @coryrylan
Angular

Sass and CSS Import Performance in Angular

Cory Rylan

- 4 minutes

Updated

This article has been updated to the latest version Angular 17 and tested with Angular 16. The content is likely still applicable for all Angular 2 + versions.

In this short post, I want to cover an everyday performance issue I see occur in Angular applications that use Sass or CSS imports. The Angular CLI supports Sass, CSS and Less to style global application styles as well as component styles. Angular components styles have a useful CSS encapsulation mechanism that ensures any component CSS is local to the component and does not globally alter any styles. Let's take a look at a simple component with some CSS styles.

<!-- duplicate-styles.component.html -->
<button>duplicate styles</button>
/* duplicate-styles.component.css */
@import './../../styles/config';
@import './../../styles/buttons';

button {
@extend .btn;
background-color: blue;
}

In this component, we have a button that needs to be explicitly styled for a feature that diverges from the standard buttons in our application. We want this button to have the standard styles of a button but also adjust the color for our specific use case. To do this, I am importing the buttons Sass file. The _buttons.scss file contains all the global button styles for my Angular application. In the component sass file, I am extending the global .btn class. So everything works! My component extends the .btn class and adds its specific feature color. Feature done right? Well not quite.

We created a performance issue with the bundle size of our application. Let's take a look at that _buttons.scss file and see what is in there.

/* _buttons.scss */
@import 'config';

.btn {
background-color: $color-theme;
padding: 12px;
color: #fff;
}

.btn.warning {
background-color: $color-warning;
}

.btn.error {
background-color: $color-error;
}

Ok, pretty standard styles in _buttons.scss so whats the issue? Well, it comes back to our specific feature component importing the _buttons.scss. Let's take a look at the compiled JavaScript from the component in the browser.

// duplicate-styles.component.ts compiled JIT output
(function(
jit_createRendererType2_0,
jit_viewDef_1,
jit_elementDef_2,
jit_textDef_3 /*``*/
) {
// All the CSS from _buttons.scss has been copied into the bundle
var styles_DuplicateStylesComponent = [
`
.btn[_ngcontent-%COMP%], button[_ngcontent-%COMP%]{background-color:#4CAF50;padding:12px;color:#fff}
.btn.warning[_ngcontent-%COMP%], button.warning[_ngcontent-%COMP%]{background-color:orange}
.btn.error[_ngcontent-%COMP%], button.error[_ngcontent-%COMP%]{background-color:red}
.btn.feature[_ngcontent-%COMP%], button.feature[_ngcontent-%COMP%]{background-color:purple}
button[_ngcontent-%COMP%]{background-color:blue}
`

];
var RenderType_DuplicateStylesComponent = jit_createRendererType2_0({
encapsulation: 0,
styles: styles_DuplicateStylesComponent,
data: {}
});
function View_DuplicateStylesComponent_0(_l) {
return jit_viewDef_1(
0,
[
(_l()(),
jit_elementDef_2(
0,
0,
null,
null,
1,
'button',
[],
null,
null,
null,
null,
null
)),
(_l()(), jit_textDef_3(-1, null, ['duplicate styles']))
],
null,
null
);
}
return {
RenderType_DuplicateStylesComponent: RenderType_DuplicateStylesComponent,
View_DuplicateStylesComponent_0: View_DuplicateStylesComponent_0
};
});

Notice in the compiled JS we see all the styles from the button.scss file embedded into the compiled JavaScript for our feature component. Angular's CSS encapsulation mechanism while powerful does not tree shake any of styles. Without tree shaking this means any unused style from an imported Sass or CSS files is bundled into that component. We have two copies of our site-wide button styles.

It can be really easy to accidentally import a large stylesheet multiple times in an Angular application. This duplication leads to unnecessary bloat in our bundles. How do we fix this issue?

High Specificity Global Styles

We have a few options to correct our duplicated styles. The first is to create a specific global style.

/* _buttons.scss */
@import 'config';

.btn {
background-color: $color-theme;
padding: 12px;
color: #fff;
}

.btn.warning {
background-color: $color-warning;
}

.btn.error {
background-color: $color-error;
}

.btn.feature {
background-color: purple;
}

Now in our template of our feature component we have the following:

<!-- global-styles.component.html -->
<button class="btn feature">global css class</button>

Now we can remove the custom component level styles all together. Now notice this isn't ideal as we are losing the benefit of local component styles. We could instead go another route and take advantage of some more advanced Sass features.

Sass Mixins

Another option is to leverage Sass mixins to create a button generator of sorts to create buttons as needed. A Sass mixin is a function that can generate CSS for us. Let's look at the _mixins.scss file.

// _mixins.scss
@mixin btn-generator($btn-color) {
// take a color as a parameter
background-color: $btn-color;
padding: 12px;
color: #fff;
}

Our btn-generator mixin will allow us to stamp our easily create buttons in our project now. Now we can remove the global specific style and use the scoped component.

// mixin-styles.component.scss
@import './../../styles/config';
@import './../../styles/_mixins';

button {
@include btn-generator(red);
}

Notice how we no longer import the _buttons.scss file? Now our component will not be duplicating the global buttons style sheet into its bundle. We use the btn-generator mixin to create an instance of button styles that will only apply to our feature component styles. Our compiled component now only bundles the CSS the mixin generated for us.

// mixin-styles.component.ts compiled JIT output
(function(
jit_createRendererType2_0,
jit_viewDef_1,
jit_elementDef_2,
jit_textDef_3
/*``*/
) {
// Only the single generate button style
var styles_MixinStylesComponent = [
`
button[_ngcontent-%COMP%]{background-color:red;padding:12px;color:#fff}
`

];
var RenderType_MixinStylesComponent = jit_createRendererType2_0({
encapsulation: 0,
styles: styles_MixinStylesComponent,
data: {}
});
function View_MixinStylesComponent_0(_l) {
return jit_viewDef_1(
0,
[
(_l()(),
jit_elementDef_2(
0,
0,
null,
null,
1,
'button',
[],
null,
null,
null,
null,
null
)),
(_l()(), jit_textDef_3(-1, null, ['mixin styles']))
],
null,
null
);
}
return {
RenderType_MixinStylesComponent: RenderType_MixinStylesComponent,
View_MixinStylesComponent_0: View_MixinStylesComponent_0
};
});

Another best practice you may have noticed was we store sass variables in a _config.scss. The _config.scss allows us to share variables across sass files, but we must ensure there is no other CSS or we will get the same duplication issue. This same rule would apply if you used custom CSS properties.

As you can see Angular's style CSS encapsulation mechanism is useful but be aware of how you use style imports with component level styles. Check out the working demo in the link below!

View Demo Code   
Twitter Facebook LinkedIn Email
 

No spam. Short occasional updates on Web Development articles, videos, and new courses in your inbox.

Related Posts

Angular

Creating Dynamic Tables in Angular

Learn how to easily create HTML tables in Angular from dynamic data sources.

Read Article
Web Components

Reusable Component Patterns - Default Slots

Learn about how to use default slots in Web Components for a more flexible API design.

Read Article
Web Components

Reusable Component Anti-Patterns - Semantic Obfuscation

Learn about UI Component API design and one of the common anti-patterns, Semantic Obfuscation.

Read Article