I want to apply dynamically SCSS styling according to a variable, known prior to Angular bootstrapping. Is it possible to change styles.scss
programmatically? In main.ts
before bootstrapping, I want to add dynamically a line like:
@import 'assets/styles/customerA/_themes.scss'; or @import 'assets/styles/customerB/_themes.scss';
depending on a variable's value coming from the server.
If the above is not feasible, is there any way to populate styles.scss
with the content of another file which will be defined on the server? I want to change styles.scss
and not any other "local" scss file.
SCSS is compiled to CSS during compile time, and SCSS variables are replaced with resolved value during compile time, which means there is no way to change the variable during run time. However, CSS variables just sits there during run time, and you can dynamically CRUD them during run time with JavaScript (Web API).
A Sass variable must be initialized with a value. To change the value, we simply replace the old value with a new one. A variable that's initialized inside a selector can only be used within that selector's scope. A variable that's initialized outside a selector can be used anywhere in the document.
It is supposed to be for changing the environment file, but I am guessing it should work also for styles. You can change the styles file using fileReplacements in angular.json, but this would require you to rebuild the app each time. Now you can run ng serve -c customerA or ng serve -c customerB to get the relevant stylesheet.
A quick and easy way to perform the migration is to use the schematic NPM package schematics-scss-migrate. Read more on what this schematic package does on NPM. A schematic to migrate from CSS to SCSS stylesheet format for an Angular CLI project npm i --save-dev…
You'll notice in their angular.json file that they have a main.scss which is used regardless of which secondary style is chosen. This file will handle any global themes you need. Notice in their main.scss they import several Sass partials. These allow them to set global styles for any theme.
Now that we have achieved that, we have to change the source to use the new files extension, as a good Angular convention suggests that we name the stylesheets of our components by suffixing them with. component.
I would recommend following the general form of the Angular Material Website: https://github.com/angular/material.angular.io/blob/master/angular.json. It's worth cloning this repo and playing around with it locally. There are several moving parts.
In your angular.json file you'll add the css as follows. Notice that you'll need to compile your scss to css separately for this to work. They do this in the tools/build-theme.sh
script. :
{ "input": "src/assets/customer-1-theme.css", "lazy": true, "bundleName": "customer-1-theme" }, { "input": "src/assets/customer-2-theme.css", "lazy": true, "bundleName": "customer-2-theme" },
Then, look at their style manager: https://github.com/angular/material.angular.io/blob/master/src/app/shared/style-manager/style-manager.ts
You'll create a function to dynamically add and remove this style sheet as needed:
function createLinkElementWithKey(key: string) { const linkEl = document.createElement('link'); linkEl.setAttribute('rel', 'stylesheet'); linkEl.classList.add(getClassNameForKey(key)); document.head.appendChild(linkEl); return linkEl; }
Then in the ThemePicker you'll set up an observable to manage the theme setup and removal. See (https://github.com/angular/material.angular.io/blob/master/src/app/shared/theme-picker/theme-picker.ts):
installTheme(themeName: string) { const theme = this.themes.find(currentTheme => currentTheme.name === themeName); if (!theme) { return; } this.currentTheme = theme; if (theme.isDefault) { this.styleManager.removeStyle('theme'); } else { this.styleManager.setStyle('theme', `assets/${theme.name}.css`); } if (this.currentTheme) { this._themeStorage.storeTheme(this.currentTheme); } }
Personally, I went about it a slightly different way, but that meant that the scss compiles into one css file, and the class is selected dynamically. Their way adds an extra build step, but allows the compiled scss to be lazy loaded.
Managing Global Styles
You'll notice in their angular.json file that they have a main.scss which is used regardless of which secondary style is chosen. This file will handle any global themes you need. Notice in their main.scss they import several Sass partials
. These allow them to set global styles for any theme. You can investigate how these are used, the main reason they are using partials here is that they can pass variables from the secondary sass files up to these partials through the use of mixins.
@mixin material-docs-app-theme($theme) { $primary: map-get($theme, primary); .docs-primary-header { background: mat-color($primary);
map-get
is a Sass function, which allows you the ability to define an object and use its values in Mixins. Material design has a sass-function they are using here, but you could use an easier method grab a custom color in your main.scss. In your style-a / style-b scss create a custom theme.
$my-custom-theme: ( background-color: #ffff, button-background: rgba(200, 200, 200, 0.6), foreground: #34383c );
Then, you would change their @include to:
@include material-docs-app-theme($theme, $my-custom-theme);
The material-docs-app-theme mixin would change to:
@mixin material-docs-app-theme($theme, $my-custom-theme) { $background-color: map-get($my-custom-theme, background-color); .our-background { background-color: $background-color; }
This allows you the opportunity to define custom colors in the individual lazy-loaded style sheets and have them available in a global main.scss. Mixins and Partials from Sass make this possible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With