Photo by Lukas Blazek on Unsplash
When working on the UI, it is common to have data drive our styling. For example, in a grid of financial data, we could have negative numbers in red and positive numbers in green. Another common request is to style data based on its position. For example, this grid from a sudoku game:
Example of a styled grid.
In this game, each cell has a color that creates a checkerboard pattern. What happens when we want to change the highlighting based on user interaction? For example, how can we highlight the row, column and 3×3 region when a user picks a cell? We should consider what happens when there are errors. How can we maintain the checkerboard pattern and apply the formatting?
The typical approach is to nest classes. So you might end up with class=”odd row error” or something similar. Then, your components would have a function toggle the classes based on the cell data. This approach works OK but can lead to expensive change detection cycles. For example, in this grid, there are 81 cells. So we need to toggle classes for each change. This change detection cascade can get very expensive very fast. Luckily there is a better way.
CSS Variables
CSS variables are potent. They provide a way to set any CSS property and interact with them from Javascript or Typescript. They follow the CSS cascade. To set up our grid, we use the following CSS variables:
https://medium.com/media/b6280bf1fe987e18bd2f86b089704ff2/href
To use our variables, we need to use the CSS function var(). For example, to set the default background color, we use the following:
https://medium.com/media/5721240904b4435618d3dc59809b15da/href
These variables don’t seem to be doing much for us. Now we have a grid with all the same background. How do we change that? We need to add a rule consuming our variable for the checkerboard color. Combining these two rules is the foundation that will allow us to do some fantastic things in the future.
https://medium.com/media/df0586d40f6fc1ba284ab3fbb07d718d/href
Attribute Binding in the Component
When something changes in the grid, we update the data using a ComponentStore. This store allows our business logic to exist outside of our components. By decoupling in this manner, our components reflect the application’s state. Each cell has a property that will tell us if it is a row, column, region, etc. We can toggle a CSS class or use another extraordinary ability of Angular.
Attribute binding allows us to bind something to an attribute on our component. This ability is essential. It will enable us to do stuff with our CSS without toggling classes on and off. It will allow us to shorten our change detection cycles because we will only update cells that have data that change. Here is how we would bind to an attribute from our component:
https://medium.com/media/4ba3d48a7decd1685c5beb62b1dd91e8/href
And in the template to bind to the attribute:
https://medium.com/media/9fb485f6177958a5888863a9033ae17a/href
The gridCellSelect is a pure pipe that sets the value to row, self, column, etc. A pure pipe is necessary because it is only evaluated when the input changes. Our attribute binding means that when the grid sets [focusState], it adds an attribute like data-focused-state=”self” for example.
Bringing It All Together
We now have a solid foundation with our CSS variables. We also have attributes on each cell, so we don’t have to toggle classes. How does this help us style our grid? The final piece is to create CSS rules that take advantage of what we have. In CSS, we can write rules that target attributes. They take the form of [attribute=’value’]. These rules mean that we can use CSS that looks like this:
https://medium.com/media/5fd3f7717c2e05b0ca6685106ede5517/href
In our project, we are using SCSS. The & joins the selector to the parent with no space. So in our example, these rules become :host[data-focused-state=’col’] for example. Notice that we don’t set any CSS properties. Instead, we change the value of our CSS variables. This works because CSS variables obey the cascade. Remember that we mentioned this was important earlier in the article. These rules are why that is important. These selectors change the variable’s value for the element that matches that selector and its children. This means that the earlier CSS rules can stay in place, and we change the colors used on the cell. This gives us the following effect:
Grid with center cell selected.
The colors are not good (and we will work on them “soon”). The concept is important. Any time a user selects a cell, the cell selected gets a highlight. The row is highlighted in green. The row is highlighted in blue. Finally, the 3×3 region has its colors for the row and column. The rest of the cells get a brownish color.
Based on profiling, the memory usage and render time for this approach are excellent. We don’t fall below 60 fps when navigating between cells. Because the data drives the highlighting, we don’t have to worry about toggling classes on and off, preventing getting into a bad state.
If you want to explore more, the repository can be found here: https://github.com/xocomil/AngularSudoku. The code from this article is in the components library. We looked specifically at the CellComponent and the GridComponent.
Thank you to Chau Tran, who introduced me to the idea on a stream and through the first PR in this repository.
Use Data To Style Your Angular Components was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.