Nyhetsbrev

Dynamic themes in Angular Material

TLDR

A working demo of the dynamic themes we create through this article can be found here, while the source code is located here.

Setting the scene

If you have used Angular Material, you may have configured your own theme at some point. You may also have observed that you can use either the mat-light-theme or the mat-dark-theme to define your theme, as such:

This will give you either a light- or dark-theme on your Angular Material components, depending on your choice. Unfortunately, there exists little information on how to change the theme dynamically, without having to refresh the page. I did a little research and found a solution to creating dynamic themes, which to many may seem obvious, while others may find it useful. Through this article we will create a minimal Angular application step by step, using Angular CLI to create a web application with dynamic themes. I will not describe the basic setup of an Angular application with Angular Material, since this is described in detail in their respective docs.

NB: Make sure that you select SCSS as your default styling extension when creating your. This can be done through an argument in Angular CLI:

ng new theme-demo --style scss

The basic stuff

After setting up the Angular application using Angular CLI, and adding Angular Material as a dependency, we’re going to make a SCSS-file for our material theme. We’re placing it in a styles-folder, just for the sake of structure since we’ll add more theme-style files later.

And then render the toolbar component in app.component.html by referencing the selector for the toolbar component:

<app-toolbar></app-toolbar>

You should now have something similar to the image below. Nice!

Almost there (regarding the setup)

As a final step in the setup of the basic application, we need to wrap our entire page with the CSS-class ‘mat-app-background’. According to the Angular Material documentation, this CSS class will provide a default background color for your application, and will also change according to your theme. We can incporporate this CSS-class in our application by adding a div-element around the content in our app.component.html:

<div class="mat-app-background">
<app-toolbar></app-toolbar>
</div>

The good stuff

At this point, we have a web page with a toolbar and a background color that is managed by Angular Material. Our next step is to add a second theme, the dark theme.

Adding our dark theme

A new theme can be configured within a CSS-class and used in your application by referencing the CSS-class in your HTML. Add a new theme in our material-theme.scss:

The reference to $light-primary-text is a SCSS-variable found in the Angular Material theming. You may of course choose font color for your dark theme as you prefer. We can now use your new theme in our app.component.html:

A slide toggle should now be visible in your toolbar. In the working demo found in the top of this article, I have taken the liberty of moving the slide-toggle to the right side of the toolbar. This is only a styling preference and can be accomplished by using flexbox on the mat-toolbar-row. See the source code for an example.

The Core piece

A common pattern in Angular for communication between distant components, is to use a shared service. This pattern (and other communication patterns) is described in detail in the Angular documentation. In regard of the Core module pattern, we can create a Core module in our application, and create a service to administer the state of the dark-theme:

ng g module src/app/core
ng g service src/app/core/services/theme

To make our theme service publish changes to all components that listen, we use rxjs:

Thats really all there is to it. This service will provide an observable isDarkTheme that components can subscribe to in order to know if the dark theme is enabled or not. Controllers that wish to change the state of the dark theme, can change it by calling the setDarkTheme-function and provide a new state. Remeber to add our theme service to the providers-array in the Core module, and finally import the Core module in the App module.

Use our theme service

Our toggle slider to enable dark theme doesn’t serve much of a purpose yet. That’s about to change. Inject the theme service in the toolbar component, and call the setDarkTheme-function each time the slide toggle changes:

This ought to do it! Toggling the slide toggle will now call setDarkTheme in the ThemeService with the new state. We have also taken the liberty of subscribing to the isDarkTheme-observable, and setting the checked property on the slide toggle to the isDarkTheme value. Notice the use of the async pipe here as well, as it is super useful for unwrapping the boolean observable, and helps us manage potential memory leaks and unexpected behavior by unsubscribing when the component is destroyed.

The finishing touch

The only thing left is to add the pieces together. We already have the dark-theme CSS-class in our app.component.html, and we have the ThemeService that emits a new dark-theme state each time it changes. Let’s inject the ThemeService in our app.component.ts:

I have also taken the liberty of adding a div with the CSS-class .content. This is simply a wrapper for the page content, adding padding around it.

The next level

Adding Angular Material components within our application will now automatically adapt to the selected theme; it’s so easy it’s not fun. That’s why we’ll take this to the next level by creating a custom component that adapts to the theme.

Create the tile

Let’s create a simple tile component for our application. This is only for illustrating the concept, you may naturally create whatever kind of component you like.

ng g component src/app/tile

Change the tile template into the following:

<div class="tile">
<p><ng-content></ng-content></p>
</div>

The ng-content element allows us to insert custom content when using the tile component. Add a tile in the app.component.html:

This mixin needs to be called each time the theme changes. This is actually the same thing that happens when we call the angular-material-theme in our material-theme.scss. Each time the theme changes, the Angular Material components is provided the new theme through a mixin, which updates the colors on all the Angular Material components. We can adapt this pattern in our own application, by creating a new SCSS-file in our styles-folder:

Final words

At the core of this article, the theme service is the secret ingredient that makes the dynamic theming possible. We have only scrathed the surface of the capabilities of this pattern, as one can imagine not only having a true/false state for the theme, but having an enum or other sorts for providing multiple themes. Or how about multiple observables that can control the theme of different components. I have also provided a second slide toggle in the working demo, to illustrate how multiple controls can change the theme, and still maintain the correct state by subscribing to the isDarkTheme observable. The possibilities here are endless, and are only limited by our imagination.

Om Erik Tallang

Erik holds a master's degree in Computer Science, and has three years of experience in the industry with front-end development as his main area of expertise. The past year has been commited to diving into Angular, and miscellaneous supplementing libraries such as Angular Material.