Theme support with Angular Material

Aaron

April 9, 2020

Introduction

Angular Material provides theme support as part of its library. The full documentation can be found here:

https://material.angular.io/guide/theming

As part of a redesign to the UI for nerd.vision we wanted to introduce some ground work before we began. This included theme support, better test coverage and responsive design. Today I wanted to talk about how we implemented theming with Angular Material from the ground up to create custom themes that requires nothing more than one file.

Setup

Firstly we can introduce our main theme.scss file in the package.json.

------------------------------------------------------------------------------------------------------------------------------------------

"styles": [
 "src/styles.scss",
 "src/theme.scss"
],

------------------------------------------------------------------------------------------------------------------------------------------

This theme.scss file is the main file for your default theme you wish to use. Below shows our custom code and I will try to explain what each part does.

In the main theme file you first need to import @angular/material/theming. This allows you to access all of the features Angular Material provides.

Next as part of the theme, we opted to import a global theme style for the typography. This allows you to potentially have the option to change font styles based on the theme.

------------------------------------------------------------------------------------------------------------------------------------------

$typography: mat-typography-config(
       $font-family: 'Source Sans Pro, Open Sans, Arial, sans-serif',
       $headline: mat-typography-level(32px, 48px, 700),
       $button:mat-typography-level(14px, 14px, 700)
);

------------------------------------------------------------------------------------------------------------------------------------------

Angular Material requires you to have at least a primary colour and accent when defining a theme. You can select this colour from angular materials colour palette or you can define your own palette. In our case we opted to use our own palette that kept in line with our existing colours for our product. To do this is as simple as creating the palette which can be done based around a single colour. There are a number of palette generators

------------------------------------------------------------------------------------------------------------------------------------------

$primary: mat-palette($md-kaleidoscope, A800);
$accent:  mat-palette($mat-yellow, 700);

kaleidoscope-theming.scss

@import '../node_modules/@angular/material/theming'; @include mat-core();

@include mat-core();

$md-kaleidoscope: (
 50 : #ebedf2,
 100 : #cdd2dd,
 200 : #afb7c8,
 300 : #909cb4,
 400 : #72819f,
 500 : #5a6884,
 600 : #455066,
 700 : #313847,
 800 : #262c38,
 900 : #1c2029,
 A100 : #505c75,
 A200 : #3b4457,
 A400 : #1c212b,
 A700 : #334e68,
 A800 : #30475e,
 contrast: (
   50 : #000000,
   100 : #000000,
   200 : #000000,
   300 : #ffffff,
   400 : #ffffff,
   500 : #ffffff,
   600 : #ffffff,
   700 : #ffffff,
   800 : #ffffff,
   900 : #ffffff,
   A100 : #000000,
   A200 : #ffffff,
   A400 : #ffffff,
   A700 : #ffffff,
 )
);

------------------------------------------------------------------------------------------------------------------------------------------

In our theme file we also opted to have a custom background colour for our dark theme. Doing this was simple with Angular Material. Just import the scss file in the top of your main theme.scss file and then you can use it

------------------------------------------------------------------------------------------------------------------------------------------

@import "kaleidoscope_colours_theme";
$background-color: map_get($md-kaleidoscope, A400);
$background: map-get($theme, background);
$background: map_merge($background, (background: $background-color));
$dark-theme: map_merge($theme, (background: $background));

------------------------------------------------------------------------------------------------------------------------------------------

This allows you to override the default dark theme background colour. Finally you can import the entire theme file using the below code

------------------------------------------------------------------------------------------------------------------------------------------

@include angular-material-theme($dark-theme);
@include angular-material-typography($typography);

------------------------------------------------------------------------------------------------------------------------------------------

To finish off our dark theme we wanted to be consistent with all the styling throughout the project. We opted to use css variables to do this and try to limit the variables to a small subset of colours that matched the theme. To add these into your theme file simple add them in as shown below

------------------------------------------------------------------------------------------------------------------------------------------

body{
 --background: map-get($theme, background);
 --primary-color: 49, 56, 71;
 --primary-lighter: 90,104,132;
 --primary-darker: 38,44,56;
 --secondary-color: 48,71,94; // side-bar-blue
 
--theme-opposite: 212, 207, 207;
 --accent: 255, 193, 7; // yellow
 
--white: 212, 207, 207;
 --black: 35, 54, 72;
 --opacity: 0.5;
}

------------------------------------------------------------------------------------------------------------------------------------------

Then in any css file you can simple refer to that colour like this.

------------------------------------------------------------------------------------------------------------------------------------------

border: 2px solid rgba(var(--theme-opposite), var(--opacity));

------------------------------------------------------------------------------------------------------------------------------------------

Because we opted to create them with RGBA, we can also add an opacity variable to keep that consistent too!

And there is it, the default theme using Angular Material theming with a host of custom additions to really provide that consistancy throughout the project. So that leaves us with adding more themes? Providing you are using the css variables across the product you can simple add in a new file and link it into the theme.scss file.

------------------------------------------------------------------------------------------------------------------------------------------

@import 'themes/nervision-light/variables';

------------------------------------------------------------------------------------------------------------------------------------------

------------------------------------------------------------------------------------------------------------------------------------------

variables.scss
@import '~@angular/material/theming';
@import "../../kaleidoscope_colours_theme";

$typography: mat-typography-config(
       $font-family: 'OpenSans-Extrabold, Open Sans',
       $headline: mat-typography-level(32px, 48px, 700)
);

//Define the default theme (same as the example above).
.light-theme {
 $light-primary: mat-palette($md-kaleidoscope, A700);
 $light-primary-accent:  mat-palette($mat-amber, 500);
 $light-primary-warn:    mat-palette($mat-deep-orange);
 $background-color: map_get($mat-blue-grey, 50);
 $light-primary-base-color: #aeaeae;
 $light-primary-theme: mat-light-theme($light-primary, $light-primary-accent, $light-primary-warn);
 $_mat-button-ripple-opacity: 1;

 --background: map-get($light-primary-theme, background);
 --dark-background: 28,33,43;
 --primary-color: 51,78,104;
 --primary-darker: 42,55,61;
 --primary-lighter: 153,166,179;
 --secondary-color: 48,71,94;
 --theme-opposite: 35, 54, 72;
 --accent: 64,152,215; // yellow
 
--white: 212, 207, 207;
 --black: 15, 24, 32;
 --opacity: 0.5;

 $background: map-get($light-primary-theme, background);
 $background: map_merge($background, (background: $background-color));
 $light-primary-theme: map_merge($light-primary-theme, (background: $background));

@include angular-material-theme($light-primary-theme);
}

------------------------------------------------------------------------------------------------------------------------------------------

Thats it, keep the variables consistent, and the rest will work flawlessly. We added in a simple function to switch out the theme which just sets a class on the body of the project to dark-theme/light-theme and thats it, everything else cascades down to change the theme. In our case with Angular it can be difficult to get hold of the body element in order to manipulate it. We were able to add this code in to listen for the theme change using a subscribe function and to append or remove the class inside.

app.component.ts

------------------------------------------------------------------------------------------------------------------------------------------

this.currentTheme.subscribe(theme => {
if (this.previousTheme) {
this.renderer.removeClass(document.body, this.previousTheme.value);
}
this.renderer.addClass(document.body, theme.value);
this.previousTheme = theme;
});

------------------------------------------------------------------------------------------------------------------------------------------

Finally I will leave the entire theme.scss file below so you can see how it all fits together

------------------------------------------------------------------------------------------------------------------------------------------

theme.scss
@import '~@angular/material/theming';
@import '~@covalent/core/theming/all-theme';
@import 'themes/nervision-light/variables';
@import "kaleidoscope_colours_theme";

$typography: mat-typography-config(
       $font-family: 'Source Sans Pro, Open Sans, Arial, sans-serif',
       $headline: mat-typography-level(32px, 48px, 700),
       $button:mat-typography-level(14px, 14px, 700)
);

@include mat-core();

$primary: mat-palette($md-kaleidoscope, A800);
$accent:  mat-palette($mat-yellow, 700);
$warn:    mat-palette($mat-deep-orange);
$background-color: map_get($md-kaleidoscope, A400);
$mat-dark-theme-foreground: mat-palette($mat-grey, 100);

$base-color: #aeaeae;

$theme: mat-dark-theme($primary, $accent, $warn);

$background: map-get($theme, background);
$background: map_merge($background, (background: $background-color));
$dark-theme: map_merge($theme, (background: $background));

@include angular-material-theme($dark-theme);
@include angular-material-typography($typography);

body{
 --background: map-get($theme, background);
 --primary-color: 49, 56, 71;
 --primary-lighter: 90,104,132;
 --primary-darker: 38,44,56;
 --secondary-color: 48,71,94; // side-bar-blue
 
--theme-opposite: 212, 207, 207;
 --accent: 255, 193, 7; // yellow
 
--white: 212, 207, 207;
 --black: 35, 54, 72;
 --opacity: 0.5;
}

------------------------------------------------------------------------------------------------------------------------------------------

Conclusion

In this blog post, I have outlined the entire process of taking Angular Material and adding in themes with custom elements and colour schemes into your project to bring consistency and to avoid the pain we had with our original UI of wanting to add in theme support but it being a mammoth task. Now that the setup is in place within our project, we can add in new themes with just a single file and a decision on the colour palette. If you wanted to take this further, you could also provide some form of colour picker that fills the variables in within the custom theme file and let the user make their own theme!

Aaron

Aaron

UI/UX developer