Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular - How to remove <style> element from <head> after the component is destroyed

Tags:

Note: I tried the example in both Angular 5 and Angular 6.

The issue

If using 'encapsulation: ViewEncapsulation.None' on an Angular component a <style> element will be appended to the <head> when component is being shown. The <style> element will never get removed, even after the component is destroyed. This is the issue. As more components are shown there are more and more <style> elements in the <head>. Ultimately this causes conflicts when there are global css rules for the same html element e.g. body, as per my example. Only the CSS from the lastly appended <style> block will be used, even if this last <style> block belongs to the component that does not exist anymore.

head styles

I would like to see a proper solution for removing from the DOM the <style> element that came along with some component. E.g. cleanup when onDestoy function from the component is triggered.

I am new to Angular and I have just stumbled upon this interesting behavior. Would be good to know if there is a simple workaround.


Example

EXAMPLE: https://stackblitz.com/edit/angular-ukkecu

Angular example

In my app I have 3 wrapper components that will be the root element of my app. Only one would be shown at the time. Shown component will determine the theme for the entire website. It should include global styles, more specifically a dedicated variant of a global style (a theme). For that reason they all have 'encapsulation: ViewEncapsulation.None'. Each global style have it's own compiled variant of bootstrap and other external plugins based on SASS variables. So having encapsulation here is no option, these are global styles and plugins.

The solution works fine the first time only until the other components are shown and <style> elements are appended to the <head>. After that only the styles from the lastly used component will be used because its <style> came last and overrides any previous styles.


Possible solutions

It seems like the only solution would be to reload the page, or not use components for switching the global theme.

like image 379
Hrvoje Golcic Avatar asked May 12 '18 22:05

Hrvoje Golcic


1 Answers

Forget about encapsulation in your case, it can't help you with your requirement. Instead use a shared service, lets call it style-service, that will add/remove the style nodes in the document head.

Instead of adding your css styles in the stylesUrls of the @Component decorator, you will add them by using the style-service on ngOnInit function, which will add the style node to the document head. Once the component gets destroyed on ngOnDestroy function, you will remove the style by using the style-service, which will remove the style node from the document head.

Enough said, lets see some code:

style.service.ts

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

@Injectable()
export class StyleService {
  private stylesMap: Map<any, Node> = new Map();
  private host: Node;

  constructor() {
    this.host = document.head;
  }

  private createStyleNode(content: string): Node {
    const styleEl = document.createElement('style');
    styleEl.textContent = content;
    return styleEl;
  }

  addStyle(key: any, style: string): void {
    const styleEl = this.createStyleNode(style);
    this.stylesMap.set(key, styleEl);
    this.host.appendChild(styleEl);
  }

  removeStyle(key: any): void {
    const styleEl = this.stylesMap.get(key);
    if (styleEl) {
      this.stylesMap.delete(key);
      this.host.removeChild(styleEl);
    }
  }
}

WrapperVariantRedComponent (from your demo)

import { Component, OnInit, OnDestroy, Renderer2 } from '@angular/core';

import { StyleService } from '../style.service';

declare const require: any;

@Component({
  selector: 'app-wrapper-variant-red',
  templateUrl: './wrapper-variant-red.component.html',
  styleUrls: [ './wrapper-variant-red.component.css']
})
export class WrapperVariantRedComponent implements OnInit, OnDestroy {

  constructor(private styleService: StyleService) { }

  ngOnInit() {
    this.styleService.addStyle('red-theme', require('../../theme/global-theme-variant-red.css'));
  }

  ngOnDestroy() {
    this.styleService.removeStyle('red-theme');
  }

}

Working (forked) DEMO from your StackBlitz sample.

like image 72
benshabatnoam Avatar answered Sep 26 '22 22:09

benshabatnoam