Designing components that have to work in diverse scenarios could be tricky. We have to think about different use cases and various business logic, depending on the context. We have to plan how the component will look, which elements it should consist of, and what styling approach we will implement. It happens that the component has to be used in various views, where it should look different while behaving in the same way in terms of logic. In such a scenario, we should focus on designing the component so that it's easy to implement different UI's while preserving component logic, preferably without much changes.
I know this sounds a bit abstract at the moment, but I will give you an illustration that hopefully will shed some light on that. Imagine you have an app that has to present some items. Let say the items are persons that contribute to some project. The persons should be presented in the form of tiles in the list view and the grid view.
I will use the person tile as an example of our problem. The tile behaves pretty much the same regardless of the context of use (list or grid). It has the same information that is received from the same place (probably from the parent component via the
@Input property). Any actions set on the tiles are the same. For example, clicking on the tile should take us to the detail view, etc.
The only difference is in the presentation of data - horizontally arranged content for the list view and a more concise design for the grid view.
I’m pretty sure you all have faced similar cases in some projects. It’s not always about the direction of data (row/column). The same problem applies to the need for different colors, fonts, sizes, and placement elements.
I would like to encourage you to take advantage of some fantastic Angular techniques that make designing such components easier and more natural. Those techniques support flexible and convenient component architecture. Such architecture makes component code easy to extend and finally brings into play more CSS styling in favor of reducing TypeScript, making it less UI-related.
This article will focus on three techniques built-in Angular which have one feature in common - styling the host element:
Angular View Encapsulation
I feel that this topic, the problem, and proposed techniques may sound weird for people who don’t know Angular and how the styles are isolated by default. So if you are an experienced Angular developer, you can skip this part, but if not, I recommend reading it - it will clear some doubts.
The first question that may come into your mind is why should I care about some advanced techniques for styling the elements if I can just write CSS rules that will style the tile from the example above differently based on the simple selector, for instance like that:
In the Angular app, you will most likely have two components that define the view - one will be the
list-view-component, and the second one will be the
grid-view-component. The tile will be a separate component called a
tile-component. To make it more clear here is an example how the HTML code could look like in this situation:
Now let’s take a closer look into the component code, to dive into the styling problem. Consider such component:
In this scenario, styles written for list and grid components will only apply to their elements but will be completely isolated from the tile styling. That's how the style isolation in Angular works, and that's why we need special techniques to handle that.
Why is style isolation important? It may look more like an impediment if you see this for the first time. Imagine a complex application where you want to implement a new feature, including new styles. If the styles aren’t isolated, you will have to be extremely careful not to override, using your styles, the styles for other app elements. Isolation keeps it limited by default to the component itself to make styling easier to maintain.
Without style encapsulation developers have to be extremely careful to don’t override styles. This leads to using special naming conventions to ensure you select only the element you want. Still, there is a chance you will end with not unique selectors that will override styles for each other. That is why in my opinion, Angular’s view encapsulation is a fabulous feature, and we should use that. Outside Angular, I would recommend trying using Shadow DOM, which provides pretty similar behavior. One closing biting remark - still, many frameworks like React don’t use the advantage of isolating the styles, which in my opinion makes it hard to develop a complex application with many components using such frameworks.
How does the view encapsulation work?
On app startup, components styles are being processed to isolate them from the others. Then all isolated styles are put into a styles file.
How is the isolation implemented? Angular attaches to each component a unique property which is then used as a selector to combine with the style rules. Let's take a closer look to the example:
Pretty basic component with input data and a single header element to display the name. In the case of styling, it has one rule for the color of the header element. How is it processed at startup?
Every time the tile component is used, Angular under the hood attaches custom property like that:
Then the styles are added to the global
<style> tag in the
<head>, but because this attribute
ngcontent exists, they are applied only to the host element of the component:
I believe now you understand how the view encapsulation works in Angular and why it's not so easy to style child components based on the context of use in different parent components.
The easiest way to set the styles for the host element is by using
:host pseudo-class selector. This method uses native CSS capabilities provided by a browser and doesn't need any TypeScript properties or logic in the component class.
The way it works is actually pretty simple. Remember the fragment on how the styles are being isolated at startup? It uses the same approach to apply styles to the host element using the mentioned selector. For instance, assume we have the tile-component. At startup, it renders like this:
Now, if we set any styles under the
:host selector, they will be applied like this:
When could such a technique be handy? For sure, when we want to set some styles on the actual host element. It’s better to set the style inside the component’s code because it should be its own responsibility to follow the designed UI, right? It doesn’t make much sense to put styles for the child component in the parent component’s stylesheet. Another case that I see often is when people are using unnecessary containers inside a component template. It doesn't give any semantics meaning. It’s just another element in the DOM. Such a scenario is perfect for using the
To clarify, we stick to our leading problem, which is creating a tile component that could be used in different scenarios:
For now let’s stick with the basic style implementation. Imagine a component written in that way:
In this case, the
<div> element isn’t needed and doesn’t provide any value. Let’s add one more bad practice into this example. Imagine that the tile element should have a border, but it can’t be set on the div because of some rules, so instead, it’s being set in the parent component like that:
Let’s see how it can all be simplified using the
:host selector. We can move border style into the tile component stylesheets by selecting it host element and use the same method to remove container element and move the styles straight into host elements:
It's simpler, more readable, doesn't push to the DOM unnecessary elements, and keeps the internal styles within the component. I didn’t change the UI in any way.
The above example shows how to apply static styling into the host element without considering unnecessary container elements or setting the styles from the parent component scope.
It is good to know a technique that helps with setting static styles, but I bet you think about more dynamic scenarios - this is where much more exciting mechanisms come into play!
:host & dynamic styling
There are cases when we need to adjust component styling to our design. If the component is from our project, it’s slightly more accessible, but imagine if the element comes from some ui-components library, for instance, Material. It’s not so easy to pierce our custom styles into prebuild components. Imagine we are still working on our tile component, and we want to give the ability to customize some areas, so it’s easier to use in differently designed applications.
I will consider changing the background of the component for an example of that technique. First, problem illustration:
There are a few ways to override default styling, so it pierces through View Encapsulation and sets our custom background color. We can put styles into the global stylesheet that is not subjected to the encapsulation process. That is not very readable. We are messing with things we don’t know with no idea what the repercussions could be. The second option is to use the old shadow piercing technique that allows overriding encapsulation in Angular. In my opinion, this is a highly dangerous approach, and I will not explain that method further.
Lastly, we have a technique that uses CSS variables to allow piercing CSS with custom values when needed. What’s essential - the developer that created the component precisely tells us where it’s allowed to make changes - so we can be sure that it won’t break something else or has any side effects. I recommended that approach. It’s worth saying it’s not only helpful for developing UI libraries, you can use that in regular projects too.
Alright, to ensure we are on the same page, a quick recap about CSS variables:
Variables should be used to remove duplications of values from our stylesheets. For example, if you use the same colors repeatedly by copy & pasting various style rules in your app, it’s far better to create a variable. Name it appropriately and meaningful, and then use the variable’s name to reference the value you assigned.
Take a look on that short snippet:
On the root scope level, I declared two CSS variables that represent two colors as my theme. That allows me to use the variables via function var, which inserts the value of our variable based on the provided name. There are many more exciting things about that method, but I will focus on this simple use to show you how it can be applied in our Angular case.
Let’s say our tile background should be transparent by default, but we want to allow others to use this component to provide any color they desire.
From the parent component perspective the HTML can look like this:
Now how to apply CSS variable technique into the Angular component solution. We need two things done. Firstly we need to change tile component so it uses the CSS variable as background color if provided. I will use the last code for tile component from :host section and implement usage of CSS variable:
As you see, I didn’t change much, I just replaced the background color value with the var function. If we want to specify a default color, it could be passed as the second argument in the var function, but let’s keep it as it is.
The second thing we need to consider is how to provide the value for
--tile-background. I will simply use CSS classes (
green) to select element and assign the variable like this:
That’s pretty straightforward, isn’t it? We just set the variables where they are needed. If they are in place, they will populate the background CSS property, and if not, nothing will happen.
It’s a great technique to allow setting custom styles in a very controlled and safe way. We, as developers, can specify exactly what can be customized by providing precise API through CSS variables.
It’s worth mentioning that many popular Angular libraries use this approach to allow customization of the elements they provide. An excellent example of that is Taiga UI which has mastered that technique definitely.
Pretty dynamic styling, huh? Well, it is, but what if Ii will tell you, we can make it even more dynamic? Using Angular properties, we can actually create and change variables on demand.
A remarkable example of that was presented by my colleague Alex:
What is so great in the above snippet idea? It shows that variable names can be provided on demand based on any condition we would like. It can be generated for each item in
*ngFor, providing a unique variable. It can be generated when some condition will be met, for example, when the user makes some action - there are many possibilities.
Using that technique, we can create complex logic in the component. We are exposing only the CSS variables that can be provided by the parent component if needed. That is incredibly useful and fantastic in the case of excellent component architecture.
Let’s move to other phenomenal techniques:
HostBinding is a decorator available in Angular to change the host's DOM properties dynamically. That decorator can be used to set any DOM property, although, in this article, we will focus on setting and changing the CSS class and a custom data attribute. That way, the properties will be put on the host element in the DOM.
When to use this technique? For example, if you need to apply different styling based on some logic conditions, it could be very convenient.
Let's stick to our primary example with the tiles view and assume we have to achieve a way to highlight some tiles.
One way to do it is to use a
HostBinding decorator and apply a class when the item should be highlighted.
In our case,
HostBinding syntax looks like this:
highlighted will be set on the host element if
promoted variable will be set to true, or removed from the element if
promoted will be falsy.
Now let's combine this with the component code to see the decorator in action:
Instead of assigning a property to
HostBinding, I've used the getter function to get the value of the promoted property directly from a person’s data to make it more dynamic and reactive on data changes.
We have two style setups, the default one with the blue background and another one that highlights the item making the background white.
This technique is pretty good if we have a boolean state, for example, highlighted and not highlighted - only two exclusive options. The class is added or removed, pretty simple stuff. What if we need to style the component according to a state which can have multiple exclusive values. For instance, consider that our tile could be in active or disabled or just regular state. That is pretty typical behaviour, right?
The answer to that problem is also to use a HostBinding decorator. We just need to replace setting the class with setting something more. When it comes to things that can be selected using CSS on the DOM element, the class is not the only option here. Data attributes were introduced in HTML5 to allow inserting custom data that needs to be associated with a particular element. It was designed openly to create any attribute you wish. The syntax is pretty simple. You have to prefix whatever you want with
data- and the element will be treated as a standard data attribute. For example
data-parent=”list” could be a custom data attribute called "parent" with a value of "list".
Enough with the theory - let’s take a closer look at how this could be used in our case. Assume that our tiles can be in a few states (exclusive). The tile could be active, for instance, when the user selects it, tile could be disabled if, for example, it cannot be selected, and finally, it can be in a neutral, regular state. Each state will have a mapping in the UI of the component. The image below illustrates that:
How could this be implemented using data attribute and HostBinding technique? Let’s start with assigning the data attribute in the component:
The attribute is set via
HostBinding to the host element. It takes a string value that indicates the current state, allowed values are:
It would be useful in some scenarios to have the ability to change the state from the outside:
Now it uses an Input decorator to be provided from the parent template, based on some condition or as before it can still be set within the tile component.
Using attribute to style component is easy, example below will show how to set the styling for tiles in the state active:
Using the same technique we can set different styling for each case of the component state. Here is a full snippet with complete component:
While those are pretty lovely ways to manipulate properties on the host element via little TypeScript code and apply styling based on that, there are cases when we can stick with only CSS syntax to provide styles for different use cases.
The last technique that I want to present to you is another pseudo-class selector, the
:host-context. This special selector will help us provide styles that will apply only in a particular context of use - based on where the component is being used. To quickly remind you of the context, here is a flat:
Assume we already have the tile component, we have the list and the grid containers, and now we have to implement slightly different UIs depending on the context of use.
Tiles inside a list view should have a big avatar on the left and then personal information in the column on the right. However, the same tile in the grid view should have all elements stacked vertically and aligned to the center.
You already know the HostBinding technique, and it could be used here to set different classes on the tile host element based on the view, which could be an Input property, and then the tile could take care of the styling.
This could look like this:
The thing is though, it doesn't make much sense to use TypeScript logic for that. Our scenario is only about the specific UI and that UI context, so it would be best if we could stay with CSS for styling as long as possible.
Actually, we can do this all in CSS using Angular-built selectors. Such an approach leaves the TypeScript Class untouched and unaware of slight UI changes. This gives us a perfect way of adjusting the UI only in a component stylesheet.
What do I mean by the context of use? For example here:
Tiles are in the context of the container with
list class or the
grid one. We can simply use that in the
:host-context selector and apply different styles based on that.
What is the syntax for the
:host-context selector? Here is a short snippet:
:host-context selector takes another selector for instance the CSS class and it uses that to check whether the current element matches that selection, if so it applies the styles. Here is a full component example:
Everything is set up in the styles without using any Angular specifics. That is a pretty lovely separation!
In the context of class
.list tile will have the display set to flex and aligned in the row, while in the context of class
.grid tile, the content will be placed in the column. The article element that keeps personal information will have slightly different styles, whether inside
I highly recommend getting to know these three techniques better and use them in your projects. The ability to style host elements of your components will give you an incredible boost in development and make your code so much cleaner.
Here is a short list to summarize the most crucial insights you have to remember from this article.
:host pseudo-class selector - use to set styling on the host element in your stylesheet
- convenient to remove unnecessary HTML elements (like containers)
- use along with CSS variables to make it customizable from outside of the component
HostBinding decorator - use to assign classes/styles/attributes dynamically to the host element
- suitable to remove code duplications from CSS and HTML
- take advantage of data attributes to make UI stateful
:host-context pseudo-class selector - use to set different styling for the host element depends on the context of the use
- suitable to adapt UI to other places in the app without duplicating components
My final thought, which I will leave with you, is:
When designing components, try to separate UI logic from the business (Class) logic.
I know it is not always possible, but if there is - it will give you a nicely structured component that is easy to understand and extend in the future. If the business logic changes, you will change the TypeScript class without wondering if it breaks the UI, if the UI will change, or the component will have to be used in a different place, you will use the techniques I described to you and simply change the styles.
I hope you find this article useful. I'm always happy to discuss some in-depth questions, just catch me on Twitter @maciej_wwojcik.
Here are the links to the StackBlitz and Github that includes all things I've shown you in the article:
Thanks for reading!