Angular supports several of the most popular CSS preprocessors, including Scss, Sass, and Less, via the Angular CLI (Command Line Interface). Whenever web developers create a new app, the Angular CLI will prompt you to choose between vanilla CSS and a preprocessor. Should you choose to employ a preprocessor, Angular will compile to CSS automatically as part of the build process.
However, if you use Webpack or similar app bundling tool, you will have to manually install plugins and set up the necessary configuration to facilitate CSS compilation according to your chosen preprocessor. This can make integrating a CSS preprocessor into your web app a real hassle. Moreover, if you employ variables that dynamically set CSS values at run-time, it might make sense to compile styles within the app itself. In this web development tutorial, we will modify this existing Angular app to read in color variables from a Less file and assign the compiled CSS attribute values to some class variables.
You might also want to check out our web programming tutorial on Color Manipulation with JavaScript.
Customizing Theme Colors
A couple of good reasons for choosing a CSS preprocessor like Less (short for “Leaner Style Sheets”) is its support for custom variables and numerous functions like lighten() and darken(). These two features allow us to define some color properties like these in a .less file:
@BackgroundColor: rgb(82, 172, 240); @HoverColor: lighten(@BackgroundColor, 20%); @FocusBorderColor: gray;
We can then reference our variables from our CSS rules like so:
.background { background-color: @BackgroundColor; } .hover-background { background-color: @HoverColor; } .focus-border-background { background-color: @FocusBorderColor; }
This provides a simple mechanism for changing an app’s color theme by swapping out one variables.less file with another.
The two code snippets will be combined into the following CSS rules when compiled:
.background { background-color: #52acf0; } .hover-background { background-color: #b0d9f8; } .focus-border-background { background-color: gray; }
Our CSS tutorial, Working with CSS Variables, goes into greater detail on how to use variables in CSS.
Reading Asset Files from an Angular Application
By default, Angular has an asset directory, located under the /src folder at the root of your project, which is copied over to the build directory by Angular CLI during the build process. It is where you put all application resources – anything from images, videos, and .json files to styles and scripts. It is defined under the “assets” section of the angular.json file:
"architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/angular-theme-changer", "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", "tsConfig": "tsconfig.app.json", "assets": [ "src/favicon.ico", "src/assets" ], "styles": [ "src/styles.scss" ], "scripts": [] }, "configurations": { // ...
You can also add instructions to copy over files from a common library in the node_modules folder if you need to:
"assets": [ "src/favicon.ico", "src/assets", { "glob": "**/*", "input": "./node_modules/@acme/common-assets/css/", "output": "./assets/" }
We will store our three color variables in the variables.less file, which we will create in the src/assets folder of our project so that we can access them later:
There are a few ways to read files in TypeScript/JavaScript, but I would recommend Angular’s HttpClient class. It is specifically for communicating with a server over the HTTP protocol. To use it, we can import the HttpClient service class from @angular/common/http and inject it as a constructor argument. Its get() method takes two arguments: the endpoint URL from which to fetch, and an options object that is used to configure the request. If web developer do not specify any options, get() expects the response body to contain JSON data. Since that is not the case here, we need to specify a responseType of “text“.
The get() method returns an Observable that we can subscribe to and process the file contents:
import { HttpClient } from '@angular/common/http'; export class AppComponent { constructor(http: HttpClient) { http.get('./assets/variables.less', {responseType: 'text'}) .pipe(first()) .subscribe( variables => { // Process the variables here... }, err => { console.log(err); }); } }
Read: HTML, CSS, and JavaScript Tools and Libraries
Converting Less Variables to a JS Object
Rather than try to parse the contents of the variables.less file ourselves, we can utilize less-vars-to-js library to convert our variables to JavaScript (JS). Specifically designed for Less variables, it converts ours to the following JS object:
{ @BackgroundColor: "rgb(82, 172, 240)" @FocusBorderColor: "gray" @HoverColor: "lighten(@BackgroundColor, 20%)" }
It can be invoked directly, without a constructor, as it only does one thing. We just need to pass in the Less variables string within the http.get’s subscribe callback:
import lessToJs from 'less-vars-to-js'; // ... variables => { let vars: Object; try { vars = lessToJs(variables); } catch(err) { console.log(err); return; }
Setting the CSS Values from the Less Variables Object
If you look at the generated JS Object, you’ll notice that Less functions like lighten() are not compiled by the lessToJs() function. That’s because less-vars-to-js is just a Less variable parser and not a Less processor. To compile Less functions into actual color values, we need to use the official, stable version of the Less npm JS library. Its methods may be invoked from the command-line, or, as in our case, programmatically. The main entry point into less is the less.render() function, which takes the following formats:
less.render(lessInput[, options]) .then(function(output) { // output.css = string of css // output.map = string of sourcemap // output.imports = array of string filenames of the imports referenced }, function(error) { }); // or... less.render(css, options, function(error, output) {})
We will be using the first syntax.
Although the options are not required, we will need to supply the modifyVars attribute in order to enable run-time modification of Less variables. It accepts a JS Object like the following:
{ '@buttonFace': '#5B83AD', '@buttonText': '#D9EEF2' }
The lessInput argument needs to be in the format of Less rules and not variable declarations. The last thing we want to do is go hunting through all of our stylesheets for rules that use the color variables. In fact, that would be counterproductive, because we are only interested in the compiled color values and nothing else. For that, we can just wrap them in some dummy rules:
let dummyCssRules = '', i = 0; for (const [key, value] of Object.entries(vars)) { dummyCssRules += 'rule' + ++i + ' { color: ' + key + '; }\n'; }
Now we have got three valid CSS rules that incorporate the Less color variables:
rule1 { color: @BackgroundColor; } rule2 { color: @HoverColor; } rule3 { color: @FocusBorderColor; }
The less render() method returns a Promise with an Object that contains, among other things, the compiled CSS string:
const options = { modifyVars: vars }; less.render(dummyCssRules, options) .then((cssObject) => { // set the theme colors... }) .catch((reason) => console.error(reason));
Upon encountering @HoverColor’s value of “lighten(@BackgroundColor, 20%)“, Less readily sets the value to lighten’s results:
rule1: {color: '#52acf0'} rule2: {color: '#b0d9f8'} rule3: {color: 'gray'}
Read: Introduction to CSS-in-JS
Assigning the CSS Colors to Component Variables
Recall that the goal of all this is to call the App Component’s existing setColors() method with the three theme color values. To do that, we’ve got one more conversion to do if we are to access the CSS rule values. Again, we should rely on a library rather than parse the CSS ourselves. A good CSS to JS converter is available to us from none other than American Express (as in the credit card). They have a few repos on GitHub, including one for the css-to-js library. It takes CSS rules in a string format and converts them to a JS Object like this one:
{ rule1: { color: "#52acf0" } rule2: { color: "#b0d9f8" } rule3: { color: "gray" } }
All that is left to do now is invoke setColors() with our object attributes:
import { convert } from '@americanexpress/css-to-js'; // In the Component constructor... const compiledCssObject = convert(cssObject.css); // Set default colors this.setColors( compiledCssObject.rule1.color, compiledCssObject.rule2.color, compiledCssObject.rule3.color );
You can take a look at the demo to see how all of the above code works together.
Final Thoughts on CSS Variables
This web development tutorial presented a few good JS libraries for converting between Less, CSS and JS, which we put to good use by setting some custom theme colors. As we will see in the next article, we can fetch entire Less stylesheets in much the same way, allowing us to entirely separate theme styles from our application.