Search code examples
angularangular-materialng-class

Angular Material Theme - Selector on BODY tag


I have an application developed in Angular with Material components. I'm using Material Theme to have a few different themes to my app (Dark, Light, Colorful).

My main component - app.component html look like this:

<div [ngClass]="appTheme">
  <router-outlet></router-outlet>
</div>

The value of appTheme can be 'Light', 'Dark' or 'Colorful'. According to this different @mixin is being executed, that will color my app in different colors (you can read more about it on the official Material site).

My problem is that some 3rd party components, and event some Material components are floating pop-ups. These pop-ups are being attached to the BODY tag. So the html look like this:

<body>
  <app-root>
    <div class="dark">
     ...
    </div>
  </app-root>
  <some-3rd-party-component>
    ...
  </some-3rd-party-component>
</body>

As you can see the class dark will not effect the some-3rd-party-component - since it isn't a child element.

How can I set the [ngClass] on the body element. so I could control 3rd party components??


Solution

  • Instead of setting the theme class manually like this <div [ngClass]="appTheme">...</div> you could set it programmatically by using pure js. You just need to get the document body and add the theme class to the list of classes:

    setBodyClass() {
      // get html body element
      const bodyElement = document.body;
    
      if (bodyElement) {
        // remove existing class (needed if theme is being changed)
        bodyElement.classList.remove(activeClass);
        // add next theme class
        bodyElement.classList.add(nextClass);
    
        this.bodyClass = nextClass;
      }
    }
    

    Here is a Stackblitz of that approach.

    Another way (but not tested by be) is mentioned in this SO answer: Angular2 add class to body tag . Here they are using Angular's document injector and renderer to change the class of the body.

    Another way which I've used to theme all sorts of different Angular Material components within overlays is the OverlayContainer from @angular/cdk/overlay. Similar to the pure js approach, but won't work on elements which are neither inside a matOverlayContainer nor the app.

    const matOverlayContainerClassList = this.overlayContainer.getContainerElement().classList;
    
    matOverlayContainerClassList.remove(previousThemeName);
    matOverlayContainerClassList.add(currentThemeName);