I been reading a few articles on this but they seem to be conflicting in several different ways. I am hoping to re-create the same theme switching as the angular material documentation site for the latest version of angular material [5.0.0-rc0]
I have two custom themes, this is custom-theme.scss and there is light-custom-theme.scss which is nearly identical, sans the mat-dark-theme
@import '~@angular/material/theming';
$custom-theme-primary: mat-palette($mat-blue);
$custom-theme-accent: mat-palette($mat-orange, A200, A100, A400);
$custom-theme-warn: mat-palette($mat-red);
$custom-theme: mat-dark-theme($custom-theme-primary, $custom-theme-accent, $custom-theme-warn);
@include angular-material-theme($custom-theme);
My styles.scss looks like so
@import '~@angular/material/theming';
@include mat-core();
@import 'custom-theme.scss';
@import 'light-custom-theme.scss';
.custom-theme {
@include angular-material-theme($custom-theme);
}
.light-custom-theme {
@include angular-material-theme($light-custom-theme);
}
And then it's called in the index.html <body class="mat-app-background">
Everything works fine when I do one theme. But I am trying to switch between the two. Adding both themes into angular-cli.json, the light-custom-theme takes over
"styles": [
"styles.scss",
"custom-theme.scss",
"light-custom-theme.scss"
],
I have the following code in place in one of my components to handle toggling themes
toggleTheme(): void {
if (this.overlay.classList.contains("custom-theme")) {
this.overlay.classList.remove("custom-theme");
this.overlay.classList.add("light-custom-theme");
} else if (this.overlay.classList.contains("light-custom-theme")) {
this.overlay.classList.remove("light-custom-theme");
this.overlay.classList.add("custom-theme");
} else {
this.overlay.classList.add("light-custom-theme");
}
}
But whenever it runs the theme remains the same. For what it is worth, there is already a "cdk-overlay-container" object at position 0 in overlay.classList
0:"cdk-overlay-container"
1:"custom-theme"
length:2
value:"cdk-overlay-container custom-theme"
I am unsure how to debug this as the angular material documentation doesn't give me too much to work with, any help would be appreciative!
Thanks!
Here's an alternative solution for Angular 5.1+/Angular Material 5.0+.
*Edit: As noted, this still works in Angular 7+.
Working editable example - https://stackblitz.com/edit/dynamic-material-theming
In theme.scss, include a default theme(notice it isn't kept under a class name - this is so Angular will use it as the 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, include OverlayContainer from @angular/cdk/overlay. You can find Angular's documentation for this here https://material.angular.io/guide/theming; though their implementation is a little different. Please note, I also had to include OverlayModule as an import in app.module as well.
In my app.component file, I also declared @HostBinding('class') componentCssClass;
as a variable, which will be used to set the theme as a class.
app.component.ts
import {Component, HostBinding } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { OverlayContainer} from '@angular/cdk/overlay';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
constructor(public overlayContainer: OverlayContainer) {}
@HostBinding('class') componentCssClass;
onSetTheme(theme) {
this.overlayContainer.getContainerElement().classList.add(theme);
this.componentCssClass = theme;
}
}
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 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 an observable so that the functionality would be more dynamic.
You have to use the getContainerElement
method of OverlayContainer
. Here's some example usage:
this.overlay.getContainerElement().classList.add('my-theme');
As for your style files, I strongly suggest removing this line for both custom-theme.scss
and light-custom-theme.scss
(you only need it for your classes in this case):
@include angular-material-theme($custom-theme); // Remove this line from custom-theme.scss and light-custom-theme.scss
If you also want to toggle the theme for your app, you should probably use this in the same toggleTheme
method:
toggleTheme(): void {
if (this.overlay.classList.contains("custom-theme")) {
this.overlay.classList.remove("custom-theme");
this.overlay.classList.add("light-custom-theme");
} else if (this.overlay.classList.contains("light-custom-theme")) {
this.overlay.getContainerElement().classList.remove("light-custom-theme");
this.overlay.classList.add("custom-theme");
} else {
this.overlay.classList.add("light-custom-theme");
}
if (document.body.classList.contains("custom-theme")) {
document.body.classList.remove("custom-theme");
document.body.classList.add("light-custom-theme");
} else if (document.body.classList.contains("light-custom-theme")) {
document.body.classList.remove("light-custom-theme");
document.body.classList.add("custom-theme");
} else {
this.overlay.classList.add("light-custom-theme");
}
}
- More info on theming
- Stackblitz demo (For the demo, I had to nest the app in a Sidenav)
You cam always check how the theme picker is being implemented in https://material.angular.io/ and just do the same https://github.com/angular/material.angular.io/tree/master/src/app/shared/theme-picker by doing so you will have the everlasting solution as in case any breaking changes you always can lookup material docs source how to fix.
With reference to @Edric's solution
Used local-storage to retain the selected theme.
Here is my Github Link where I have updated the working code:~
Angular Material Theme Changer
Hope this is also helpful.