Get Material 2 theme color scheme/palette for other elements
I found an awesome workaround!!!! I'm so excited to show this because its been bugging me how to implement this for ages. So here goes; First, change all of your css files to scss;
For existing projects
Run in console
ng set defaults.styleExt=scss
(ng set seems to have been depreciated, but you can check out this for a fix thanks to user @wlyles get/set have been deprecated in favor of the config command )
- Rename all existing
.css
files to.scss
- Manually change the file extention of
styles
in.angular-cli.json
from .css to.scss
- If you didnt use a tool like WebStorm Refactor to rename then manually change all the
styleUrls
from.css
to.scss
For future projects
Just for your new project simply use
ng new your-project-name --style=scss
For all new projects to use scss use
ng set defaults.styleExt=scss --global
Now you will need to have a theme.scss file in your app root like so:
Now in your style.scss file you want to add the following (as you can see I referrence background-color but you can change this to any element to theme your site however you want):
EDIT: You dont NEED to put this custom @mixin element in your styles.scss
you can put it in any one of your *name*.component.scss
and then simply import and include it the same way you do with the example given!
@import '~@angular/material/theming';
// Define a custom mixin that takes in the current theme
@mixin theme-color-grabber($theme) {
// Parse the theme and create variables for each color in the pallete
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
$warn: map-get($theme, warn);
// Create theme specfic styles
.primaryColorBG {
background-color: mat-color($primary);
}
.accentColorBG {
background-color: mat-color($accent);
}
.warnColorBG {
background-color: mat-color($warn);
}
}
Now go to your theme.scss file that you use to theme your Material 2 items, if you need help theming check this out: Material 2 Github - Theming guide
Now open your theme.scss and import your style.scss, since my theme.scss is within the root of the /src/app/theme.scss
folder I must first go out of it to reference my /src/styles.scss
global styling file like so;
@import '../styles';
Then we must actually include our new custom @mixin
we created in ALL our themes (if you have multiple like I do, so it changes color according to current selected theme).
Include it above the actual angular-material-theme include, like so:
@include theme-color-grabber($theme);
@include angular-material-theme($theme);
If you have any themes like me add it in the same position like so:
.light {
$light-primary: mat-palette($mat-blue, 200,300, 900);
$light-accent: mat-palette($mat-light-blue, 600, 100, 800);
$light-warn: mat-palette($mat-red, 600);
$light-theme: mat-dark-theme($light-primary, $light-accent, $light-warn);
@include theme-color-grabber($light-theme);
@include angular-material-theme($light-theme);
}
You can see I added my theme-color-grabber
above the include, it doesnt really matter if its above or below the actual theme because its getting the themes colors which is the main point.
My whole themes.scss looks like this:
@import '~@angular/material/theming';
//We import our custom scss component here
@import '../styles';
@include mat-core();
$theme-primary: mat-palette($mat-red);
$theme-accent: mat-palette($mat-deep-orange, A200, A100, A400);
$theme-warn: mat-palette($mat-red);
$theme: mat-dark-theme($theme-primary, $theme-accent, $theme-warn);
//
@include theme-color-grabber($theme);
@include angular-material-theme($theme);
.light {
$light-primary: mat-palette($mat-blue, 200,300, 900);
$light-accent: mat-palette($mat-light-blue, 600, 100, 800);
$light-warn: mat-palette($mat-red, 600);
$light-theme: mat-dark-theme($light-primary, $light-accent, $light-warn);
@include theme-color-grabber($light-theme);
@include angular-material-theme($light-theme);
}
And finally we can now call on our themes color for a background ANYWHERE!, for instance I give a mat-grid-tile
the 'primary' color (it doesn't take the color='' argument, like other elements such as mat-toolbar) by simply setting its class to the appropriate class name like so:
EDIT: In each of your components scss files, you will need to import '<path-to>/theme.scss'
in order for your theme to apply to that component. Don't import theme.scss
in styles.scss
because that will create an import loop!
<mat-grid-list cols="4" rows="4" rowHeight="100px">
<mat-grid-tile
colspan="4"
rowspan="5"
class="primaryColorBG">
<div fxLayout="column" fxLayoutAlign="center center">
<h1 class="title-font">Callum</h1>
<h1 class="title-font">Tech</h1>
</div>
<p>
Ambitious and ready to take on the world of Information Technology,<br>
my love for programming and all things I.T. has not wavered since I first got access<br>
to my fathers computer at age 9.
</p>
</mat-grid-tile>
</mat-grid-list>
Finally our result will look like this!:
Red theme active
Blue theme active
I personally put those in css4 variables so I can use those without imports like so
background: var(--color-primary)
And here is how to set up the css4 variables
@import '~@angular/material/theming';
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat-core();
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$app-primary: mat-palette($mat-blue);
$app-accent: mat-palette($mat-orange);
$app-warn: mat-palette($mat-red);
$app-success: mat-palette($mat-light-green);
// Create the theme object (a Sass map containing all of the palettes).
$app-theme: mat-light-theme($app-primary, $app-accent, $app-warn);
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include angular-material-theme($app-theme);
$primary: map-get($app-theme, primary);
$accent: map-get($app-theme, accent);
:root {
--color-primary: #{mat-color($app-primary)};
--color-accent: #{mat-color($app-accent)};
--color-warn: #{mat-color($app-warn)};
--color-success: #{mat-color($app-success)};
}
now colors can be used in css files without import with
background: var(--color-primary)
UPDATE:
A new version of this solution was published here:
https://github.com/mirismaili/angular-material-dynamic-themes
If you only need the answer of the asked question, probably is better to refer to the first version of the answer, below. Also, I recommend to read this section of the above repo's documentation: Use material themes for other elements.
But if want other capabilities you see in the below video, I recommend this new approach.
Live demo:
https://stackblitz.com/github/mirismaili/AngularMaterialDynamicThemes
Thank StackBlitz
ARCHIVED ANSWER:
stackblitz here
Most important parts:
In your styles.scss
(or themes.scss
if you have):
@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);
}
...
}
Dynamic-theme-changing, using setTheme()
in typescript (see here and 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 things in stackblitz.
CAVEAT: Adding 8 dynamic material themes to the app (4 lights + 4 darks) increased the size of built styles.css
by ~420 kB
in my case (compared with one static material theme).