In this article, I articulate my thoughts and the results of my experimentation on how to switch themes using Angular Material.
Angular Material has not only great components, but it has a very robust and mature way to handle your applications' themes. But, what is a Theme? Well, it's a set of colors. So, to create a theme you first have to define the colors by creating Palettes.
Since this article is not about explaining the details of a material theme (hues, pallete, etc), I will jump directly to the main topic which is how to switch themes.
Multiple Themes in One File
An application might need to have more than one theme (dark, light) and we can do so by defining the theme details in the same file. To avoid a rule overriding other theme rules, we have to have a style wrapper on top of each theme. Let's see an example as it has been described in the official Angular Material documentation.
At the end of this example, the class dark-theme is a wrapper class and applies, well, the dark theme. So, if for example we have these buttons:
This will be the result:
If we however apply the CSS class rule dark-theme as seen below:
This will be the result:
It works just fine! Right?
To find the best solution we (developers) tend to exaggerate and dive deeper into the analysis chaos. So, I did the same :)
My thought was; "How about if I have 20 different themes? OMG, the bundle will be increased and the app will have performance issues. I should find a solution". Although it seems to be a very rare and edge case, I followed this idea.
Load Multiple Themes In Different Bundles
To load multiple themes in our Angular app, we can create different bundle files for each theme. In the angular.json file, in the build target, in the styles array, we can define the paths and set the inject to false as seen in this link and in the code below:
The code below is the src/assets/dark-theme.scss
And the code below is the src/assets/light-theme.scss
If we ng serve we will see the dark-theme and the light-theme part of the lazy chunk files
Since these files are not part of the main bundle and haven't been injected (loaded) on the page load, we need to find a way to load them manually.
We can create a service, like below, which will be responsible to append a stylesheet link into the head.
To make sure this works as expected, I created two different buttons. One for the light theme, and one for the dark theme.
So I could say that this is a solution to the imaginary application that requires 20 themes 🤓
20 buttons or a drop down menu would do the trick! But wait a minute, what about if I want to apply a theme on each page?
Different Theme on Each Route
Since the main functionality to apply a theme from a lazy chunk exists, we can use it and apply different themes on each route.
My first thought was to use this service on the module level. The problem with this approach is that once we load the module, we can not unload it. There is no hook similar to the onDestroy. And why would we need a hook like this?
Well, let's consider the following; We have the Module Dark and the Module Light. Each module has one component page and gets the related theme (dark or light). We load the Module Dark and we make use of the ThemeLoaderService. The theme is applied successfully. Then we load the Module Light and again we make use of the ThemeLoaderService. The theme is applied successfully. So far so good! We now want to go back to Module Dark and we are noticing that this module has already been loaded and we can not run the ThemeLoaderService. So we got stuck with the latest applied theme.
So we need a mechanism which runs when we activate a page component, and it also runs when we deactivate a page component. 💡 We can use the CanActivate and CanDeactivate route guards in our routing module.
The routes would have to select which theme to use and make use of the route guards.
The ThemeLoader CanActivate guard will pick the data.themeName and use it in the ThemeLoaderService to apply the theme.
Let's assume that we have the admin route in which we have set the dark-theme.
We also have the dashboard route in which we have NOT set any theme. We need a way to load a default theme in the routes which haven't explicitly set any theme.
When we first load the application we need to have a theme and we can do so in the app.module.ts as seen in the code below
The animated gif below depicts how this works.
To find the the code of this experimentation you can have a look at and clone the repo https://github.com/profanis/ng-material-theme-loader
Thanks for reading my article.