This article is mainly intended for beginners. I've added a TL;DR; below as a precautionary measure, in case you're a more advanced Angular Developer. Please feel free to navigate to a section that is most interesting to you.
- Why a Theme Switch?
- The Setup
- Implementing the Theme Switch
- Next Steps
- Closing Notes
Why a Theme Switch?
Alright! So Dark Mode is a not so new cool feature. Most of the websites out there are implementing it as custom themes in their Apps.
And why wouldn't they do that, right? Dark Mode is awesome! It's easier on the eyes, better in terms of power consumption, and can provide an excellent user experience(especially in low-lighting conditions)
Yeah! You heard me. Low-lit ambiances are the best suited for Dark Mode. I mean, nobody's stopping you from changing to Dark Mode even in well-lit environments. But still, Dark Mode is easier on the eyes and makes more sense IN THE DARK.(see what I did there?)
Also, I mentioned Dark Mode as a way to enhance UX right? Well, there are multiple aspects to it. It's always better to offer users a way to customize the look and feel of your Apps, without compromising with your design language. And most of the Web Apps(or Apps, in general), do that via themes.
The other aspect might look more or less like a gimmick to some of you. But you can take the UX of your Web App, to the next level, by intelligently switching themes, based on the ambient lighting conditions that the user is in. I'll get back to this later.
One of my favorite websites, that implement Themes is the Angular Material Site. You might have seen this switch that lets you change the theme on the website.
We'll pretty much replicate the same effect in our Angular App. So without further ado, let's get started.
I've set-up Angular Material on StackBlitz that you can use as a starter template:
From here on, let's add a few Angular Material Components that we can use to see something on the UI. I'll add a toolbar, an icon on it, a menu for theme options, and a button.
Since all these Angular Material Components will be used in my
AppModule, it would make sense to create a separate
AppMaterialModule that re-exports all the Material related modules from it.
And now I can add the
AppMaterialModule to the
imports array of my
I'm doing this in here because I will be using all the Angular Material Components exposed by these Angular Material Modules in my
AppModule. This would not make much sense in a Real-World Application as we would generally not use all the Angular Material Components in all our Modules. So creating a single
AppMaterialModule and then importing it in every Angular Module that you create can lead to performance hits. So you might want to avoid that in such cases.
Moving on, I should now be able to use these Angular Material Components in my App. The look that I'm going for is really simple. THIS
Judging from the image above, we need a
MenuComponent that opens upon clicking the ? icon and the rest would already be accommodated by our Sample StackBlitz.
I plan to make this a smart component. You can learn more about the Smart and Dumb Component Pattern from this video by Stephen Fluin.
Alright, now continuing with our
HeaderComponent, it needs to pass on some options for the menu to the
MenuComponent. Each option would have things like,
headingColor for the icon to show on each menu item; and a
label, and a
value corresponding to each label.
Now we do know that Angular Material has 4 such pre-built themes named:
So we'll need 4 items for options. To avoid hard-coding of these options in the component itself, I'll just expose this data as a json file and store it in the
assets folder in a file named
options.json. Doing that will allow me to fetch it with path
This file would look something like this:
Considering that this data was exposed as a REST API, I can then fetch it using
HttpClient in my App. And that's what I'll be doing in this article. Alternatively, you can also leverage it as a static asset instead of exposing this data via a REST API. If you do that, you can import it directly in the
import options from 'path-to-options.json'. You'll just have to set
true in the
compilerOptions, in your
tsconfig.json. I have an implementation for this version in this sample StackBlitz just in case you're interested.
Okay. Let's carry on. Now, since I also have the structure of the
option Object, I can create an
interface for static typing. Let's store it in a file named
Perfect! Now the responsibility of the
HeaderComponent is to:
- Render the header(Obviously!)
- Fetch the options and give it to the
But we do need to also change the theme at some point. So it's better that we abstract the whole business logic related to themes in a service that I'd call
ThemeService. So let's implement that first:
Sweet! We can now inject this service as a dependency in the
HeaderComponent which would look something like this:
As you can see, the
HeaderComponent is also responsible for changing the theme now.
And the template would look like this:
Notice how instead of
subscribeing to the
Observable in the Component Class, we've used the
async pipe to unwrap it. This is a pattern that makes Angular reactive and as far as possible, you should follow this pattern. Once we get the options, we can then pass it as an input to the
Also, since the responsibility of changing the theme is also taken up by the
HeaderComponent, we can implement the
MenuComponent as a dumb/presentational component. So let's do that now.
So now we can tell that the
MenuComponent would accept
options as an
@Input and then iterate through them to render these options. We can also clearly see that it has a
@Output property that calls the handler with the newly selected theme. So we can implement the
MenuComponent Class like this:
And the template would look something like this:
Alright! Now we have everything in place. We just need a way to switch themes. How do we do that?
Implementing the Theme Switch
This is the final piece of the puzzle. And we can do this in several different ways. But the Angular Material Website has already implemented this right? And the good thing is, it's open-source. So we do have access to the source code.
So instead of trying to re-invent the wheel, I'm going to cheat a little bit and see how Angular Material Docs App did it.
How Angular Material Website does it?
If you check the actual implementation, they have implemented something called a
ThemePicker. This is what we see at the top-right, in the header.
This(as the name suggests) is responsible for switching the theme on the website. This component calls a service called
What does this service do, you might ask. Well, when you change the theme from the
- Checks whether there's a link tag on the HTML Document with a
classattribute, the value of which is:
- If there isn't such a
linktag, it adds this
linktag to the head of the document, and then set the
hrefproperty with the selected theme path on it.
- If there is such a
linktag, it then, it simply sets the
hrefproperty on this
linktag to the selected theme path.
Great, now that we understand what the
StyleManager does, I can just copy the
StyleManager service in my project. Once I do that, I can just inject this in my
ThemeService and call the
setStyle method from it with the appropriate values and it should ideally work.
So let's try it out.
I'll first copy the
style-manager.ts in a file named
Great. So now that I have this service in place, as planned, I'll inject this service as a dependency in my
ThemeService and implement the
All I'm doing here is calling the
setStyle method from the
StyleManagerService with the name of the style key(theme in this case), and the value of the
href attribute that it has to set.
setStyle method, again, either creates a new
link tag and then sets the
href attribute on it; or updates the
href attribute on a pre-existing
And that's pretty much it. This is what our final code looks like.
Perfect! We now have a theme switch, just like the one on the Angular Material Website. And it works as expected.
This is all great. But wouldn't it be awesome if our App could automatically switch themes based on the ambient light? Well, that's exactly what we're going to do in the next article. Check it out here.
I’m grateful to Martina Kraus, and Rajat Badjatya for taking the time to proofread it and providing all the constructive feedback in making this article better. I hope this article has taught you something new related to Angular. If it did share this article with your friends who are new to Angular and want to achieve something similar.
This article was originally published by me under the Angular Publication on DEV.TO