Angular 2 Material Dynamic Themes

I created my own scss theme and declared it in angular-cli.json, everything works fine.

Now I need to dynamically change the theme.

I tried adding a second theme to angular-cli.json, but as expected, it overrides the first.

Thus, perhaps one of the options would be to remove the topic declaration from angular-cli.json and have 2 components, each of which has its own scss-style, one overrides the other, the only difference between them is the Urls style.

Or is there another recommended way to dynamically load scss?

+18
source share
5 answers

Starting with Angular 5.1, this way I achieved dynamic theme changes.

* Edit: this still works with Angular 7+

Working editable example - https://stackblitz.com/edit/dynamic-material-theming

In my theme.scss file, I include the default theme (note that it is not stored under the class name - so Angular will use it by default), and then a light and dark theme.

theme.scss

@import ' ~@angular /material/theming'; @include mat-core(); // Typography $custom-typography: mat-typography-config( $font-family: Raleway, $headline: mat-typography-level(24px, 48px, 400), $body-1: mat-typography-level(16px, 24px, 400) ); @include angular-material-typography($custom-typography); // Default colors $my-app-primary: mat-palette($mat-teal, 700, 100, 800); $my-app-accent: mat-palette($mat-teal, 700, 100, 800); $my-app-theme: mat-light-theme($my-app-primary, $my-app-accent); @include angular-material-theme($my-app-theme); // Dark theme $dark-primary: mat-palette($mat-blue-grey); $dark-accent: mat-palette($mat-amber, A200, A100, A400); $dark-warn: mat-palette($mat-deep-orange); $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn); .dark-theme { @include angular-material-theme($dark-theme); } // Light theme $light-primary: mat-palette($mat-grey, 200, 500, 300); $light-accent: mat-palette($mat-brown, 200); $light-warn: mat-palette($mat-deep-orange, 200); $light-theme: mat-light-theme($light-primary, $light-accent, $light-warn); .light-theme { @include angular-material-theme($light-theme) } 

In the app.component file, I include the OverlayContainer from @ angular / cdk / overlay. You can find Angular documentation for this here https://material.angular.io/guide/theming ; although their implementation is slightly different. Please note, I also had to include OverlayModule as an import in app.module.

In my app.component file, I also declared @HostBinding('class') componentCssClass; as a variable that will be used to set the theme as a class.

app.component.ts

 import {Component, HostBinding, OnInit} from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Version } from './classes/version'; import { OverlayContainer} from '@angular/cdk/overlay'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent implements OnInit { constructor(private http: HttpClient, public overlayContainer: OverlayContainer) {} title = 'app'; version: Version; @HostBinding('class') componentCssClass; ngOnInit() { this.getVersion(); } onSetTheme(theme) { this.overlayContainer.getContainerElement().classList.add(theme); this.componentCssClass = theme; } getVersion() { this.http.get<Version>('/api/version') .subscribe(data => { this.version = data; }); } } 

app.module.ts

 import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatCardModule } from '@angular/material/card'; import { MatButtonModule } from '@angular/material/button'; import { AppComponent } from './app.component'; import { OverlayModule} from '@angular/cdk/overlay'; @NgModule({ declarations: [ AppComponent, ], imports: [ BrowserModule, HttpClientModule, BrowserAnimationsModule, MatCardModule, MatButtonModule, OverlayModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule {} 

Finally, call the onSetTheme function from your point of view.

app.component.html

 <button mat-raised-button color="primary" (click)="onSetTheme('default-theme')">Default</button> <button mat-raised-button color="primary" (click)="onSetTheme('dark-theme')">Dark</button> <button mat-raised-button color="primary" (click)="onSetTheme('light-theme')">Light</button> 

You might consider using observable so that the functionality is more dynamic.

+27
source

I found my answer in the topic of material design changes for Angular 2 . There is a good GIT example at https://github.com/jelbourn/material2-app .

Therefore, I use the same scss theme file, but I added a new class to it for the new theme:

 .m2app-dark { $dark-primary: md-palette($md-pink, 700, 500, 900); $dark-accent: md-palette($md-blue-grey, A200, A100, A400); $dark-warn: md-palette($md-deep-orange); $dark-theme: md-dark-theme($dark-primary, $dark-accent, $dark-warn); @include angular-material-theme($dark-theme); } 

This one is used in html and is active or not depending on the value of the logical value:

  <md-sidenav-layout [class.m2app-dark]="isDarkTheme"> 
+6
source

UPDATE:

A new version of this solution has been published here:

https://github.com/mirismaili/angular-material-dynamic-themes

Video


ANSWER ARCHIVE:

Thanks @ K.Waite. I used his / her answer . I tried to improve this. The most important editing is to use .replace() instead of .add() for classList (in setTheme() ). There are also some other features that you can see below:


enter image description here

stekblits here


The most important parts:

In your styles.scss (or themes.scss if you have one):

 @import ' ~@angular /material/theming'; @include mat-core(); @mixin define-css-classes($theme) { @include angular-material-theme($theme); $primary: map-get($theme, primary); $accent: map-get($theme, accent); $warn: map-get($theme, warn); $background: map-get($theme, background); $foreground: map-get($theme, foreground); // CSS THEME-DEPENDENT-STYLES ARE HERE: .theme-dependent-colors { background: mat-color($primary); color: mat-color($accent); } } /** * Define your custom themes in this map. * The 'key' of each member is the name of CSS class for that theme. * To better understand the schema of the map, see '@each' loop below and especially pay attention to 'map-has-key()' functions. */ $app-themes: ( indigo-pink : (primary-base: $mat-indigo, accent-base: $mat-pink), deeppurple-amber: (primary-base: $mat-deep-purple, accent-base: $mat-amber), pink-bluegrey : (primary-base: $mat-pink, accent-base: $mat-blue-gray, is-dark: true), purple-green : (primary-base: $mat-purple, accent-base: $mat-green, is-dark: true), ); @each $css-class, $theme in $app-themes { $primary: if(map-has-key($theme, primary), map-get($theme, primary), mat-palette(map-get($theme, primary-base))); $accent: if(map-has-key($theme, accent), map-get($theme, accent), mat-palette(map-get($theme, accent-base))); $warn: if(map-has-key($theme, warn), map-get($theme, warn), mat-palette( if(map-has-key($theme, warn-base), map-get($theme, warn-base), $mat-red) )); .#{$css-class} { @include define-css-classes(mat-light-theme($primary, $accent, $warn)); } .#{$css-class}-dark { @include define-css-classes(mat-dark-theme($primary, $accent, $warn)); } .theme-primary.#{$css-class} { background-color: mat-color($primary); } ... } 

In typewriting (see here ):

 import {Component, HostBinding} from '@angular/core'; import {OverlayContainer} from "@angular/cdk/overlay"; const THEME_DARKNESS_SUFFIX = '-dark'; export class AppComponent { @HostBinding('class') activeThemeCssClass: string; isThemeDark = false; activeTheme: string; setTheme(theme: string, darkness: boolean = null) { if (darkness === null) darkness = this.isThemeDark; else if (this.isThemeDark === darkness) { if (this.activeTheme === theme) return; } else this.isThemeDark = darkness; this.activeTheme = theme; const cssClass = darkness === true ? theme + THEME_DARKNESS_SUFFIX : theme; const classList = this.overlayContainer.getContainerElement().classList; if (classList.contains(this.activeThemeCssClass)) classList.replace(this.activeThemeCssClass, cssClass); else classList.add(cssClass); this.activeThemeCssClass = cssClass; } constructor(overlayContainer: OverlayContainer) { this.setThemeClass('indigo-pink', false); // Default theme } } 

See other stuff at stackblitz .


CAVEAT: adding 8 dynamic themes to the application (4 light sources + 4 features) in my case increased the size of the built-in styles.css by ~420 kB (compared to one theme of static material).

+2
source

You can switch between themes by adding or removing the css class (including the material theme) for the body tag at runtime based on the current theme.

e.g. step 1.

add id to the body tag in the html file so you can feed element by element.

 <body id="themeTag"> <app-root></app-root> </body> 

step 2.

create a second theme in your scss file, a file that is included in angular.json in angular 6 and .angular-cli.json in the angular version below 6.

 @include mat-core(); $primary: mat-palette($mat-blue); $accent: mat-palette($mat-yellow); $warn: mat-palette($mat-red); $light-theme: mat-light-theme($primary, $accent, $warn); @include angular-material-theme($light-theme); $dark-theme: mat-dark-theme($primary, $accent, $warn); .dark-theme { // css class for dark theme @include angular-material-theme($dark-theme); } 

step 3.

when you click the button, change the body tag class

 toggleTheme(){ this.isDarkTheme = !this.isDarkTheme; if(this.isDarkTheme){ /* here themeTag is id of body tag and dark-theme is css class created in theme file */ document.getElementById('themeTag').classList.add('dark-theme'); }else{ document.getElementById('themeTag').classList.remove('dark-theme'); } } 
+1
source

@K Waite (not enough comments to comment ..)

Starting with Angular 8 (maybe earlier?)

I found it necessary to add an additional line classList.remove(this.componentCssClass); . During testing, I noticed that when applying dynamic theme changes to pop-up modal windows, the modal window will only apply the latest theme in classList. Therefore, switching from dark to light themes can lead to erroneous conditions.

Inclusion of a list of classes with two elements ( default-theme and current active theme, this is solved)

 onSetTheme(theme) { this.overlayContainer.getContainerElement().classList.remove(this.componentCssClass); this.overlayContainer.getContainerElement().classList.add(theme); this.componentCssClass = theme; } 
0
source

Source: https://habr.com/ru/post/1265435/


All Articles