Reputation: 5930
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.
Upvotes: 33
Views: 33372
Reputation: 5930
After having struggled a lot with it, I found the answer in CSS variables
. That's exactly what I was looking for! You can change them during runtime and therefore dynamic theming is doable. This is a very good article describing the steps in Angular:
https://medium.com/@amcdnl/theming-angular-with-css-variables-3c78a5b20b24
Upvotes: 8
Reputation: 6986
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.
Upvotes: 23
Reputation: 1716
Just an idea that might work. (origin)
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.
projects > public > architect > build > configurations
"customerA": {
"fileReplacements": [
{
"replace": "src/styles.scss",
"with": "assets/styles/customerA/_themes.scss"
}
]
},
"customerB": {
"fileReplacements": [
{
"replace": "src/styles.scss",
"with": "assets/styles/customerB/_themes.scss"
}
]
}
projects > public > architect > serve > configurations
"customerA": {
"browserTarget": "public:build:customerA"
},
"customerB": {
"browserTarget": "public:build:customerB"
},
Now you can run ng serve -c customerA
or ng serve -c customerB
to get the relevant stylesheet.
When you are ready for production, you can either deploy manually twice, or try following this idea for translating an angular app.
P.S. If it is only a few styles that you need to change, I might suggest that you use css variables.
Upvotes: 2
Reputation: 3149
I have 2 ideas you could play around with...
1) Angular components have a stylesUrls that can take .scss files. However, it doesn't appear that you can easily replace the stylesUrl property. You can however, easily swap out the component itself...
Basically your actual app will be wrapped in a "shell" app that only responsibility is to pull in the correct .scss file. More specifically, it would be wrapped in 2 different shells, one for each style.
@Component({
selector: 'style-one-page',
templateUrl: './app.component.html',
styleUrls: ['./app-style-one-page.scss']
})
export class WrapperStyleOneComponent extends PageComponent {}
@Component({
selector: 'style-two-page',
templateUrl: './app.component.html',
styleUrls: ['./app-style-two-page.scss']
})
export class WrapperStyleTwoComponent extends PageComponent {}
Then it is trivial to swap out the 2 components.
2) My second idea would be to leverage the Angular Material Themes, they can be easily swapped out as such:
@import '~@angular/material/theming';
@import 'themes-palettes';
@import 'themes-common';
@include mat-core();
@function custom-light-theme() {
@return (
primary: $primary-palette,
accent: $accent-palette,
warn: $warn-palette,
foreground: $foreground-palette,
background: $background-palette,
is-dark: false
);
}
$custom-light-theme: custom-light-theme();
@include angular-material-theme($custom-light-theme);
.dark-theme {
$mat-dark-theme: mat-dark-theme($dark-primary-palette, $dark-accent-palette, $dark-warn-palette);
@include angular-material-theme($mat-dark-theme);
}
Upvotes: 0
Reputation: 1284
ngOnInit() {
// Do some stuff to figure out which css url you want
this.cssUrl = '/assets/styles1.css';
}
In your html file
<link rel="stylesheet" [href]='sanitizer.bypassSecurityTrustResourceUrl(cssUrl)'>
Upvotes: 1