Creating Themeable Components in Angular 2+
You have been building beautiful nice-looking web applications using cascading style sheets (CSS) or any other CSS pre-processor extension language like SasS/SCSS/Less. The awesome part is, Angular supports using any one of these options in your Angular app. In this article, we are going to make use of basic CSS & SCSS. Angular CLI has out-of-the-box SaaS support. We will learn a naive approach of building themes and themeable components in Angular. Theme? A theme is a collection of property settings that allow you to define the look of components ( as well as views ), and then apply the look consistently across all views in a single page application or across several apps.
Side note: This article is not about styling. Its about themes. You can expect to learn techniques of how to build themeable components in your Angular app.
To start with, we will consider a simple application. The application has 2 ( very similar ) custom components and we will see how to build and switch between more than one theme.
Here is a working Plnkr in case you want to jump and play with the initial setup:
Although, not very ideal but I have tried simulate a real world setup where you have 2 components both styled in a similar fashion. The 2 components look similar ( because obviously you have styled them that way) but we haven't yet introduced the notion of theme in our app. Even the existing CSS is not part of a theme. What we would ideally like to do is take the current the styles and scope them under a theme name. This way it will be easier for us to choose between default theme and any other theme. I am going to name the default theme as 'light' and a new theme will be named 'dark'. We should be able to switch between the two at any time, either programmatically or in a declarative fashion.
Let's first look into the different available states for these components and then we will jump
right into the code.
Here are the states for custom input with styling:
And the custom button component has these different states with styling:
Now let's concentrate more on the template & style side of things. Here is how the template & css looks like for these components:
Not including custom button component template and css (for brevity) but its very similar to custom input. You can look at them in the above working Plnkr.
Let's refactor our component to make it themeable
First things first: Separate Structural/Dimension Styles from Themeable Styles
In order to build theme or make your components themeable, you need separate our structural styles from themeable styles. Structural styles are css style propertiesrelated to height, width, padding, margin e.t.c. In most cases, we are not going to change these type of properties in different themes. In other words, anything that you would not like to change between one theme and the other. Themeable styles on the other hand are styles that you want to switch as the theme changes. These generally include font colors, border colors, background colors e.t.c. One important think to keep in mind for real world scenarios is its not necessary that you always move all color properties. There may be times in which you want to keep certain colors exactly the same in all themes. We will see a example of this but simulating a scenario that we want to keep the exact same stylings for disabled state.
- Take out all structural directives and move them to a separate common file. Let's name this file common.component.css for now.
- Create a new component level but theme specific file. Name it custom-input-light.component.css.
- Move out all themeable styles to the new custom-input-light.component.css
- Still keep the component level main style file custom-input.component.css but delete all styles from it.
- Create a new file custom-input-dark.component.css by cloning custom-input-light.component.css and make appropriate styling changes specific to this new theme.
In the empty custom-input.component.css file, import the following in sequence:
- First, import common.component.css
- import all other theme specific style sheets in sequence.
After above refactoring, here is how the code looks like:
and another theme custom-input-dark.component.css
Again skipping changes to custom button component but it follow the exact same style breakup steps we took for custom input component and you can check them in the final Plnkr provided in the end.
Using :host-context() pseudo-class selector
We have made some good progress. We separated out styles that we do not want to make themeable and the ones we want to make themeable. The next step to make use of :host-content pseudo-class selector to make our component theme enabled. It is exactly made for the scenario of building themes. It works just like the function form of :host(). The :host-context() selector looks for a CSS class in any ancestor of the component host element, up to the document root. Let's look at an example to understand:
Let's assume the above styles are part of some component X. This style, in the final rendered HTML, will look at parents of component X and its parent and its parent until the root document which can be the <body> tag or a container tag like the app component container or any other wrapper within your application. If it finds any of these ancestors having a class 'theme-light', it will apply these styles to all <p> within this component X. The 'theme-light' string token is a theme selector class ( 'Theme selector class' is not an official term but I believe it serves the purpose.)
Now since we are a little confident on our knowledge of :host-context(), let's apply the same technique to both light & dark theme css files. In the custom-intput-light.component.css we will add a theme selector class of 'light' and in the custom-intput-dark.component.css we will ad a theme selector class of 'dark'.
If you want to learn more about special pseudo selector classes available in Angular, you can read it here on angular.io
All we have to do now is apply special host selector scope to both dark and light theme stylings. Since we aren't using SCSS at this time, the css code might look a little verbose but that's not what we are learning here. We can always go back and clean it up with SCSS or any other pre-processor. Let's look at the light and dark theme files for custom input component look like after applying Angular's special :host-context with a special .light & .dark classes.
Here is the light theme file:
and here is our dark theme file:
Now we will apply either the class 'light' or 'dark' to any container to switch between the two available themes. I will use the main <div> of app component which acts as a container for custom input and custom button elements. Currently, the main app component template looks like this (Just look at line 1 for now):
Using [ngClass] we will simply apply classes and toggle between them on the container div. Here is how the updated template looks like(Just notice change on line 1 for now):
The last thing is to define the 'theme' variable on app component and switch it whenever. I created a simple method setTheme() on app component and two buttons to set themes, one to unset. You can now look at three <a> tags we have in the template above. Here is the final AppComponent code:
Wohoo! Both our components are now themeable! Here is a final Plnkr to play with:
Few important considerations before we close:
- We did not use SCSS in these examples. Angular in general & Angular CLI specifically supports SCSS. In real world projects, I would highly recommend using any pre-processor styling language.
- I highly recommend creating a separate theme even for default styles as done in this article. Some folks disagree to this idea. I have seen web developers create themes only for specific styles. In that case, either your application is using default styles i.e no themes or switch to using a specific theme. I don't recommend that. Why? Because doing so doesn't leave your application's style architecture in a good state. Using a theme for default styles make the application's style management consistent and themeable at all times.
- This is a naive way of managing themes and making your components themeable in Angular. If we make use of style pre-processors, we can make our themeable code more cleaner and way more powerful. By making use of variables, mixin, partials and other scss constructs, we can build more scalable themes and themeable angular components.
I am planning to do an article on Angular CLI's support for SCSS and we will explore other theming options in that write-up.
In this article, we explored how to build themeable Angular 2+ components. We also learned about structural and themeable styling properties and how to separate them out into themes & common styles. We finally used :host-context pseudo selector to make our Angular component theme aware.
Feel free to leave a comment, question, suggestion and corrections. Until next time, Happy learning!