Angular dynamic theming with Boostrap 5
Github Link: Your repo code is waiting here
In this article, lets have a look how we can create a dynamic theming based on the dropdown of company name user has selected.
But before that, lets rewind one important concept of frontend that all frontend technologies need to be compiled to a common format which browsers understand. Browsers understand only HTML, Javascript and CSS for creating webpages. So we shall use this concept.
Lets Quickly create an angular project, I shall be using angular 13 because it takes less effort to manage modules. From angular 14 standalone concept came in angular and there we will have to create module architecture explicitly. so lets not take that much burden for this article.
But Dont worry, creating dynamic theme remains same for all angular versions.
So lets start!!!!!!!!!!
Step 4 will add bootstrap, popperjs, jquery and npm-run-all.
Popperjs/core helps when we create responsive navbars in bootstrap those icons need popperjs/core to open and close the menu items in mobile view.
npm-run-all is important as it will help us to run multiple npm scripts at the same time. We shall see in below discussions where we shall need it.
Booooooooooooomm!!! dependecies are done now. Now currently we just added them but without configuring them it is like setting up mobile but not setting language. So lets configure our dependecies.
So for that open angular.json and do like below:
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.scss"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"node_modules/@popperjs/core/dist/umd/popper.min.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js"
]
You will find two styles and two scripts array in angular.json. One for development and one for testing. Add like this at both places if you have neat and clean e2e tests 😉
Now open project location in terminal and run ng serve. Remove every content from app.component.html and add this line:
<button class="btn btn-primary>Primary</button>
And save oviously. If button is blue then great 😊 our bootstrap is fine and jumping higher else please recheck the steps :( .
Always remember any changes in configurations is done, then its better to re run ng serve again and again. Its big story, one article is not enough for this. So just Do this insane thing 😊
To create dynamic themes lets create three files - lets go with three themes and you can add as many as you want. Open src/assets/ and create folder themes in it. Here create all theme files - default-theme.scss, company1-theme.scss, company2-theme.scss and company3-theme.scss
To override styles, we just need to customize bootstrap default colors for different variables. something like below:
$primary: #006699; // override primary color of bootstrap
$secondary: #009966; // override seconday color of bootstrap
$success: #66ccff; // and so on
$info: #3399ff;
$warning: #ffcc00;
$danger: #cc3300;
$light: #f2f2f2;
$dark: #333333;
// Import Bootstrap to apply the custom variables
@import "../../../node_modules/bootstrap/scss/bootstrap";
Dont forget to import boostrap from node_modules at the last. only then overriding will happen properly.
Recommended by LinkedIn
If you remember, I mentioned all browsers just understand HTML, CSS and javascript. So for theming we shall use that concept. In index.html we shall add theme css file in head tag and again replace with the one which needs to be activated via the service - next para is about it. something like below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>AngularArchLearning</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link
id="theme-link"
rel="stylesheet"
href="assets/themes/default-theme.css"
/>
</head>
<body>
<app-root></app-root>
</body>
</html>
by default we have added default theme.
Also notice the path for default theme above in code, its assets/themes/ -> here we shall generate all themes for different companies and pick and and replace in head tag in index.html with the one which needs to be activated.
Now lets respect system design (which we could avoid 😊 ) - Separation of concern and create a separate service which will be called everytime company name is changes via dropdown. And that particular company's theme will get activated.
import { Injectable, Renderer2, RendererFactory2 } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class ThemeService {
private renderer: Renderer2;
private themeLinkElement!: HTMLLinkElement;
constructor(
private rendererFactory: RendererFactory2,
private http: HttpClient
) {
this.renderer = this.rendererFactory.createRenderer(null, null);
this.initializeThemeLinkElement();
}
private initializeThemeLinkElement() {
this.themeLinkElement = this.renderer.createElement('link');
this.themeLinkElement.rel = 'stylesheet';
this.themeLinkElement.id = 'theme-link';
this.renderer.appendChild(document.head, this.themeLinkElement);
}
setTheme(theme: string) {
const themeUrl = `assets/themes/${theme}-theme.css`;
this.themeLinkElement.href = themeUrl;
}
}
How is the default theme being created. That is child of app.component.ts logic + the above service. below is how app.component.ts looks:
import { Component, OnInit } from '@angular/core';
import { ThemeService } from './services/theme.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
title = 'angular-arch-learning';
constructor(private themeService: ThemeService) {}
ngOnInit(): void {
this.themeService.setTheme('default');
}
applyTheme($event: any): void {
this.themeService.setTheme($event.target.value);
}
}
Here on ngOnInit we are passing default as variable which will be mapped by service as default-theme.scss.
And we shall also set a dropdown in app.component.html which will change themes based on company:
<select class="form-select" (change)="applyTheme($event)">
<option value="default">Default Theme</option>
<option value="company1">Company 1 Theme</option>
<option value="company2">Company 2 Theme</option>
<!-- Add more options for other themes -->
</select>
Now we are almost done. Now when application will load for first time, app.component.ts will set default theme like this:
this.themeService.setTheme('default');
This will reach to service where a link tag will be created and added into head tag as per the logic of our service
<linkid="theme-link"rel="stylesheet"href="assets/themes/default-theme.css"/>
But now the last question comes how css is coming into picture if we had opted for scss. then just look at package.json where we have added few scripts;
"scripts": {
"ng": "ng",
"build-themes": "sass --no-source-map --load-path=node_modules src/assets/themes:src/assets/themes",
"build": "ng build",
"start": "npm-run-all --parallel watch-themes serve",
"watch-themes": "npm run build-themes && sass --no-source-map --watch src/assets/themes:src/assets/themes",
"serve": "ng serve"
},
whenever we do npm run start, it adds a watcher on theme. And inside watch-theme script we are building themes with (build-themes script) and then sass script next to itwill execute scss files from src/assets/themes and create css file for each theme places in this folder in same folder.
so basically our theme folder will contain two files for each company theme, one in scss format and one for css format.
Whenever we change theme watch-theme plays role and rebuilds everything automatically and all themes keep on updating.....
Seems you enjoyed this concept. then why not follow me: 😊
Awesome to see our Ingersoll Rand team giving back to the community. Cheers to you, Bimlendu! 👍 🙂