Dynamically Creating CSS Classes in Angular

Angular provides a lot of ways for you to set your styles programmatically using NgStyle and NgClass attribute directives. It’s almost as if there is no reason for you to find another way to set and apply styles on your components. So, why the need to dynamically create CSS classes?

When you are using third party libraries, there are quite a few out there that goes out of the norm of setting your styles via the conventional NgStyle or NgClass directives. Instead, they can provide style functions or if you are unlucky, only class functions. And when your style is based on limitless conditions dictated by your data, it is bad to hard code your class and styles out of the possible combinations of how your data looks like.

The solution I have is based on the plain JavaScript solution in StackOverflow and create a StyleService so it can be readily available anywhere in the application. Below is the full code of the StyleService:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class StyleService {

  private _headElement: HTMLHeadElement;
  private get headElement(): HTMLHeadElement {
    if(!this._headElement && document.getElementsByTagName("head").length > 0) {
      this._headElement = document.getElementsByTagName("head")[0];
    }
    return this._headElement;
  }

  private _cssStyleSheet: CSSStyleSheet;
  private get cssStyleSheet(): CSSStyleSheet {
    if(!this._cssStyleSheet) {
      if(!document.styleSheets || this.headElement === null) return null;

      // Get the first style sheet that is enabled and mediaText is empty or screen.
      this._cssStyleSheet = Array.from(document.styleSheets).find(s => !s.disabled && (s.media.mediaText == "" || s.media.mediaText.indexOf("screen") !== -1)) as CSSStyleSheet;
  
      // If the style sheet doesn't exist yet, then create it.
      if(!this._cssStyleSheet) this._cssStyleSheet = this.createCssStyleSheet();
    }

    return this._cssStyleSheet;
  }

  constructor() { 
    
  }

  public setStyle(selectorText: string, styleName: string, value: string): void {
    let rule: CSSStyleRule = this.getStyleRule(selectorText);
    if(!rule) return;

    rule.style[styleName] = value;
  }

  public setStyles(selectorText: string, styles: { [styleName: string]: string } | CSSStyleDeclaration) {
    let rule: CSSStyleRule = this.getStyleRule(selectorText);
    if(!rule) return;

    Object.keys(styles).forEach(styleName => {
      rule.style[styleName] = styles[styleName];
    });
  }

  private createCssStyleSheet(): CSSStyleSheet {
    // Create the style sheet element.
    let styleSheetElement = document.createElement("style");
    styleSheetElement.type = "text/css";

    // Append the style sheet element to the head.
    this.headElement.appendChild(styleSheetElement);
    return styleSheetElement.sheet as CSSStyleSheet;
  }

  private getStyleRule(selectorText: string): CSSStyleRule {
    if(!this.cssStyleSheet) return;
    let rules: CSSRuleList = this.cssStyleSheet.cssRules.length > 0 || this.cssStyleSheet.rules.length == 0 ? this.cssStyleSheet.cssRules : this.cssStyleSheet.rules;
    let rule: CSSStyleRule = Array.from(rules).find(r => r instanceof CSSStyleRule && r.selectorText.toLowerCase() == selectorText.toLowerCase()) as CSSStyleRule;

    // If the selector rule does not exist, create it.
    if(!rule) { 
      let ruleIndex = this.cssStyleSheet.insertRule(selectorText + "{ }", rules.length);
      rule = rules[ruleIndex] as CSSStyleRule;
    }

    return rule;
  }
}

The StyleService exposes two methods:

  • setStyle – to set a single style to a selector text
  • setStyles – to set multiple styles to a selector text

Usage is simple. To set a single style, just specify the selector, the style property name, and its value:

styleService.setStyle(".class1", "background-color", "blue");
styleService.setStyle(".class1", "font-size", "24px");
styleService.setStyle(".class1", "color", "yellow");

To set multiple styles, you have two options. One is to pass an object of style property name (as key) and value pair:

styleService.setStyles(".class1", {
  "font-weight": "bold",
  "text-decoration": "underline",
  "padding": "20px",
  "background-color": "red"
});

Another way is to pass as a strongly type CSSStyleDeclaration object:

styleService.setStyles(".class1", {
  backgroundColor: "green",
  fontSize: "48px",
  border: "1px solid red"
});

Just a quick explanation of the StyleService code:

  1. First, we get from the document’s collection of stylesheets that which is not disabled and with an empty or screen media type.
  2. If we didn’t find the stylesheet that we want, you have to create the stylesheet and append it to the head element.
  3. Now, the stylesheet has either cssRules or rules property. Traverse through the list returned by any of these properties to find the selector that we want.
  4. If a rule is found, simply set the style.
  5. Otherwise, create the rule in the stylesheet before setting the style.

So, there you have it! You now have another way of setting styles to your Angular components or HTML elements.

Leave a Reply

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