Dynamic Styling with JSS – HTML Goodies

The Introduction to CSS-in-JS web development tutorial presented some of the benefits of CSS-in-JS and elucidated why it is one of the hottest trends in web development. As it turns out, one of main advantages of combining CSS with JavaScript is that you can instantly change the appearance of many page elements with a few lines of code. While dynamic styling alone is not reason enough to jump on the CSS-in-JS bandwagon, if you have decided to give it a go for some of its other benefits, you may want to set styles on the fly. Case in point, the Using an Angular Service to Read Sass Variables Demo that we partially transitioned to CSS-in-JS allowed the user to set some theme colors at runtime:

CSS-in-JS tutorial

CSS-in-JS tutorial

Hence, that app is the perfect candidate for test driving the dynamic styling capabilities of JSS, which is a framework agnostic library that we utilized to implement CSS-in-JS.

Linking JSS Rule Instances to the DOM

In the Introduction to CSS-in-JS web programming tutorial, we learned that the method to create a style sheet is the aptly named createStyleSheet(). In addition to the styles object, we can also pass it some options. We will not be going over all of the options here, except for link. It s a Boolean that links jss Rule instances with the native DOM CSSRule counterpart so that styles can be modified dynamically via attribute functions. It is false by default because it has some performance costs, but we have got good reason for activating it in our app.

Here is a recap of the jss initialization and stylesheet generation code from last time, along with the extra options parameter:

private stylesheet: StyleSheet; ngOnInit(): void { jss.use(jssPluginNested()); jss.setup(preset()); // Compile styles, apply plugins. this.stylesheet = jss.createStyleSheet(styles, {link: true}).attach();
}

Now we are ready to update some CSS Rules.

The JSS update() Function

Taking a look at the feed.component.styles.ts file that we created in the last article, you can see that the three last properties use a function to set their values:

export const styles: Object = { newsImage: { cursor: 'pointer', marginTop: '25px', display: 'flex', width: '10rem', height: '10rem', marginRight: '0.6rem', marginLeft: '0.8rem', borderRadius: '1rem', backgroundColor: styles => styles.newsImage.backgroundColor, '&:hover': { backgroundColor: styles => styles.newsImage['&:hover'].backgroundColor, }, '&:focus': { 'border-color': styles => styles.newsImage['&:focus'].borderColor } }
};

That is how JSS supports dynamic styling. You could try to set a property directly like so:

this.stylesheet.getRule('newsImage').style['height'] = '5rem';

However, that would not affect the underlying stylesheet, only the JSS rule mapping.

The JSS way to modify CSS rule values on the fly is to call the StyleSheet instance’s update() function. It sets one or more property values for a rule at once. So, to set our three colors, we would invoke update() as follows:

this.stylesheet.update({ newsImage: { backgroundColor: this.backgroundColor, '&:hover': { backgroundColor: this.hoverBackgroundColor }, '&:focus': { outline: this.focusBorderColor } }
});

Each property’s callback function then receives the passed Rule object as an attribute of the larger styles object, just as we defined our styles. If you omit a dynamic property from a rule, that property’s value will evaluate to undefined. That’s not a problem for top-level properties like the backgroundColor; it will simply undo any existing value. However, nested properties such as the two pseudo-classes will throw an error similar to the following:

Error: styles.newsImage['&amp:focus'] is undefined

As the error message sums up so eloquently, that happens because we are attempting to access the properties of an undefined object.

One way around that, other than supplying all of the dynamic values, is to provide a fallback within the callback function:

export const styles: Object = { newsImage: { // ... '&:focus': { outline: styles => styles.newsImage['&:focus'] ? styles.newsImage['&:focus'].outline : '#CCCCCC' } }
};

We’ll have to provide some initial values if we want the news image to have some default background and border colors when the page first loads. Since we are already initializing jss in ngOnInit(), when the Feed Component receives the @Input variables, we can set them in there:

private stylesheet: StyleSheet; ngOnInit(): void { jss.use(jssPluginNested()); jss.setup(preset()); // Compile styles, apply plugins. this.stylesheet = jss.createStyleSheet(styles, {link: true}).attach(); this.updateColors();
}

The updateColors() method contains the call to this.stylesheet.update() method above so that we can invoke it whenever the user switches color radio buttons.

Read: Dynamic Text Styling in Angular

Updating Colors

Clicking a radio button updates the underlying color variable in the App Component, which triggers the ngOnChanges event in the Feed Component. There, we can test for changes to our color variables and invoke updateColors() accordingly:

ngOnChanges(changes: SimpleChanges): void { if ( this.stylesheet && (changes.backgroundColor || changes.hoverBackgroundColor || changes.focusBorderColor ) ) { this.updateColors(); }
}

Notice that we must also verify that the stylesheet has been initialized because the first ngOnChanges event fires for the initial @input values, which happens before ngOnInit.

Exploring the Demo

You can look over the code that updates the News Image colors in the stackblitz demo. It is the same one that was introduced in the Introduction to CSS-in-JS tutorial, as it already included the update functionality.

Final Thoughts on CSS-in-JS Styling

In a real-world application, the management of JSS colors would benefit from being done in a service, so that the Feed Component does not have to concern itself with the methods and structure of the JSS object. I would suggest moving all of the JSS initialization code and updateColors() method to the service. UpdateColors() would be especially easy to move because the changes parameter could simply be passed along to the service function.

Read more JavaScript web development tutorials.

Leave a Reply

Your email address will not be published.