The above gif is the challenge we are going to conquer. Main tasks are:

  1. Have two squares, one reflecting primary color, the other reflecting secondary color.
  2. Title text “angular-dynamic-theme-example” has primary color, and secondary color on hover.
  3. Have two inputs and one save button to dynamically change the two colors.

Approach of solution: Put color variables in JS and bind with ngStyle

  1. Have two JS variables primaryColor and secondaryColor and update the two variables when save button is hit.
  2. Bind the two variables to the template through ngStyle

Big problem of this solution — Scss files not have access to color variables, leading to:

Colors can’t be applied to pseudo class or pseudo element (such as h1::before div::after p::first-line a:active input:disabled button:hover etc.), so task 2 can’t be done.

SCSS is compiled to CSS during compile time, and SCSS variables are replaced with resolved value during compile time, which means there is no way to change the variable during run time. So,  If you have your design system implemented with scss variables like $primary-color and $secondary-color and they are applied everywhere. You will have to deprecate them and do a big refactoring.

CSS Variables to the Rescue

“CSS-Variable” is a standard which enables you to have variables in CSS.

:root {
  --first-color: #488cff;
  --second-color: #ffff8c;
} 

div {
  background-color: var(--first-color);
  color: var(--second-color);
}

Difference between CSS variables and SCSS variables

SCSS is compiled to CSS during compile time, and SCSS variables are replaced with resolved value during compile time, which means there is no way to change the variable during run time. However, CSS variables just sits there during run time, and you can dynamically CRUD them during run time with JavaScript (Web API).

Example:
//app.component.scss

$dynamic-colour: var(--dynamic-colour);

:root{
   --dynamic-colour: #5260ff;
}

h1 {
  color: $dynamic-colour;
}


.block {
  width: 100px;
  height: 100px;
}

.primary-background {
  background-color: $dynamic-colour;
}

Then we can change the color dynamicaly that way : ( With Javascript).

//app.component.ts
  changeColor(color:string){
   document.documentElement.style.setProperty('--dynamic-colour', color);
}

As you can see, we can use Web API document.documentElement.style.setProperty to set CSS variables.

Setting up the themes

Let us create a theme.config.ts file where we will set all the themes. We can make a static config like this or maybe get the config from an API response. The latter is the better approach if you make changes to your themes often.


export const THEMES = {
  default: {
    primaryColor: 'hsl(185, 57%, 35%)',
    secondaryColor: 'hsl(0, 0%, 22%)',
    textOnPrimary: 'hsl(0, 0%, 100%)',
    textOnSecondary: 'hsl(0, 0%, 90%)',
    background: 'hsl(0, 0%, 100%)',
  },
  dark: {
    primaryColor: 'hsl(168deg 100% 29%)',
    secondaryColor: 'hsl(161deg 94% 13%)',
    textOnPrimary: 'hsl(0, 0%, 100%)',
    textOnSecondary: 'hsl(0, 0%, 100%)',
    background: 'hsl(0, 0%, 10%)',
  },
  netflix: {
    primaryColor: 'hsl(357, 92%, 47%)',
    secondaryColor: 'hsl(0, 0%, 8%)',
    textOnPrimary: 'hsl(0, 0%, 100%)',
    textOnSecondary: 'hsl(0, 0%, 100%)',
    background: 'hsl(0deg 0% 33%)',
  },
  spotify: {
    primaryColor: 'hsl(132, 65%, 55%)',
    secondaryColor: 'hsl(0, 0%, 0%)',
    textOnPrimary: 'hsl(229, 61%, 42%)',
    textOnSecondary: 'hsl(0, 0%, 100%)',
    background: 'hsl(0, 0%, 100%)',
  },
};

Theming service

We create a service and call it ThemeService. The logic for updating the themes will be handled by this service. We can inject the service into the application and then change the theme using a function we expose from the service.

COPY
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { THEMES } from '../config/theme.config';

@Injectable({
  providedIn: 'root',
})
export class ThemeService {
  constructor(@Inject(DOCUMENT) private document: Document) {}

  setTheme(name = 'default') {
    const theme = THEMES[name];
    Object.keys(theme).forEach((key) => {
      this.document.documentElement.style.setProperty(`--${key}`, theme[key]);
    });
  }
}

How this works is basically by overriding the CSS variable values that we have defined in the styles.scss file.

The function takes the name of the theme to apply. What it does it, get the theme variables for the selected theme from our config file and then loop through it wherein we apply the new values to the variables.

Summary

  1. CSS itself has its variable mechanism to enable you write CSS value by reference.
  2. CSS variable is just there during run time while Sass variable is replaced by resolved value during compile time.
  3. CSS variable can be dynamically CRUD’ed during run time with JavaScript Web API. It can also be defined initially in CSS/SCSS

Additional notes

  1. CSS variable is not natively supported by IE , but polyfill is available.
  2. You can set fallback value in case the variable is not define
.header {
  color: var(--header-color, blue); 
/* if header-color isn’t set, fall back to blue*/
}

3. CSS variable can be directly referred by SASS variable, but SASS variable cannot be directly referred by CSS variable. e.g.

// RIGHT, CSS variable can be directly referred by SASS variable
$primary-color: var(--my-primary-color);


$accent-color: #fbbc04;

:root {
  // WRONG, will not work in recent Sass versions.
  --accent-color-wrong: $accent-color;

  // RIGHT, will work in all Sass versions.
  --accent-color-right: #{$accent-color};
}
Reference:

https://medium.com/angular-in-depth

By Shabazz

Software Engineer, MCSD, Web developer & Angular specialist

Leave a Reply

Your email address will not be published. Required fields are marked *