37

I am building an application but I want to keep a consistent color scheme that can be changed with settings so I'm using Angular-Material, but I am not sure how to get the color scheme on elements that don't directly offer the ability to color them with color="primary" so I'm left with trying to figure out how to obtain the color/color scheme that my Material theme uses. I want it to change when the theme changes, for instance, my navbar will adapt to the theme change because its set to

<mat-toolbar color="primary" class="fixed-navbar mat-elevation-z10">

But a grid element from Material doesn't take the same argument so I'm left to attempt to style it in a close enough color or simply not match it at all (and it won't adjust to theme changes) as seen here:

enter image description here

I want it to match the color with the theme mat, which is here (and gets changed on options selected in navbar settings)

@import '~@angular/material/theming';

@include mat-core();   
    
$candy-app-primary: mat-palette($mat-red);
$candy-app-accent:  mat-palette($mat-deep-orange, A200, A100, A400);
$candy-app-warn:    mat-palette($mat-red);
$candy-app-theme: mat-dark-theme($candy-app-primary, $candy-app-accent, $candy-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.
.default {
  @include angular-material-theme($candy-app-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 angular-material-theme($light-theme);
}
@include angular-material-theme($candy-app-theme);
Zoe
  • 27,060
  • 21
  • 118
  • 148
Cacoon
  • 2,467
  • 6
  • 28
  • 61

8 Answers8

45

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)
Ced
  • 15,847
  • 14
  • 87
  • 146
43

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: where themes are

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 Red theme

Blue theme active enter image description here

Cacoon
  • 2,467
  • 6
  • 28
  • 61
  • 3
    Awesome, it's possible! – P.S. Oct 15 '17 at 23:19
  • 3
    :3 Yeah im so glad, its been really driving me up the wall but now I can make all my stuff work ;D – Cacoon Oct 15 '17 at 23:20
  • Good job, Cacoon ;) – P.S. Oct 16 '17 at 01:10
  • can you give complete code, please? I don't understand where I went wrong – Santosh Mar 30 '18 at 11:52
  • 1
    I am creating an import loop with this. So you sad, the them colors will be usable anywhere when you import in styles.scss. But I have to import my theme in the styles.scss and when I import the styles.scss into my theme, I am getting an import loop. So how can this work? – Florian Leitgeb Apr 27 '18 at 22:15
  • 1
    What i mean by anywhere is any of the individual scss for each visual component generated, you dont need to import 'Theme.scss' into style.scss. – Cacoon Apr 30 '18 at 01:38
  • Why have they made something that was so simple - to change a css color, so difficult. Everything else about the library makes difficult things simple. – trees_are_great Oct 23 '18 at 13:03
  • As mentioned in [Theming your Angular Material app](https://material.angular.io/guide/theming#multiple-themes) you should not include `mat-core` and `angular-material-theme`multiple times. [Theming your custom component...](https://material.angular.io/guide/theming-your-components) explains how to to do it. As I understand it, you create a component's theme with a mixin that takes a theme object and extracts the primary, accent and text colors. This theme then must be imported into the main theme file. – Chris Oct 29 '18 at 11:03
  • 1
    Thanks for this excellent guide! One thing to note though is that `ng set` is deprecated. [This question](https://stackoverflow.com/questions/50223165/get-set-have-been-deprecated-in-favor-of-the-config-command) should clear up any issues though. – wlyles Jan 22 '19 at 21:34
5

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.

Video

Thank StackBlitz


ARCHIVED ANSWER:

enter image description here

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).

Mir-Ismaili
  • 13,974
  • 8
  • 82
  • 100
  • Boy this is awesome!! – JoeCool Mar 08 '19 at 20:49
  • 2
    I have to ask again: Is it really true that you have to go through all of that just to be able to use your material theme colors in your custom html markup? And only some "special" material components provide a "color="-parameter that lets you set their color to a value from the theme (primary, accent, etc)? thx – Jens Mander Jul 25 '19 at 20:59
  • @JensMander; Have you read this section: [Use material themes for other elements](https://github.com/mirismaili/angular-material-dynamic-themes#use-material-themes-for-other-elements-non-material-elements)? If yes and not found your answer, let me know. – Mir-Ismaili Jul 25 '19 at 23:05
  • 2
    "Again" was to mean "although the discussion seems to have come to an end already, I'd like to add one question of which I believe evereyone can easily answer it except for me". @Mir-Ismaili: Thanks for the link. So it is necessary to go to that lot of trouble just to be able to use the themes colors. Thats crazy and strange to me. Such a sophisticated framework and then you have to do things like 30 years ago ;-) BTW: The problem is not that what you have to do was too difficult. It is that you think you are doing something wrong if it is unexpectedly complicated. I just couldnt believe it. – Jens Mander Jul 26 '19 at 14:03
4

Anyone looking for implementing mixin in Angular Material 12+

theme.scss:

    @use 'sass:map';
    @use '@angular/material' as mat;

    /// Gets the CSS property and it's computed value for both light and dark themes.
    /// @param {String} $property The css property to set. ex: background, color, background-color, border-color etc.
    /// @param {String} $color Theme color. Accepted values are: primary, accent, or warn.
    /// @param {String | Number} $hue The hue from the palette to use. If this is a value between 0 and 1, it will be treated as opacity. Ex values: 500, 500-contrast, darker, darker-contrast
    /// @returns {CssProperty} CSS property with it's computed value for the both light and dark themes.
    @mixin get-theme-color-property($property: null, $color: null, $hue: null) {
      // Get the color config from the theme.
      $light-color-config: mat.get-color-config($light-theme);
      // Get the required color palette from the color-config.
      $light-color-palette: map.get($light-color-config, $color);
    
      // Get the color config from the theme.
      $dark-color-config: mat.get-color-config($dark-theme);
      // Get the required color palette from the color-config.
      $dark-color-palette: map.get($dark-color-config, $color);
      @if $hue != null {
        // Finally get the desired color with the specified hue.
        $light-color: mat.get-color-from-palette($light-color-palette, $hue);
    
        // Finally get the desired color with the specified hue.
        $dark-color: mat.get-color-from-palette($dark-color-palette, $hue);
        & {
          #{$property}: $light-color;
        }
    
        .dark-theme & {
          #{$property}: $dark-color;
        }
      } @else {
        // Finally get the desired color with the specified hue.
        $light-color: mat.get-color-from-palette($light-color-palette);
    
        // Finally get the desired color with the specified hue.
        $dark-color: mat.get-color-from-palette($dark-color-palette);
        & {
          #{$property}: $light-color;
        }
    
        .dark-theme & {
          #{$property}: $dark-color;
        }
      }
    }

Usage:

@use '/path/to/theme' as theme;
.example {
  padding: 10px 20px;
  @include theme.get-theme-color-property(background, primary);
  @include theme.get-theme-color-property(color, primary, default-contrast); // or 'lighter-contrast' or 'darker-contrast'
  color: #fff;
}

<mat-toolbar class="example">
Saurabh Palatkar
  • 3,242
  • 9
  • 48
  • 107
3

I'm an absolute newbie and maybe this is an out of good practices, useless for this case, or a resource consuming solution, but at myangularthemefile.scss, i had created these classes:

@import '~@angular/material/theming';
@include mat-core();

...

.matcolorprimary{
    color: mat-color($mitjans-primary)
}

.matcoloraccent {
    color: mat-color($mitjans-accent);
}

.matcolorwarn {
    color: mat-color($mitjans-warn);
}

And I add them to those html elements I need at the component's template.

I supose it would be easy to create the same structure for background-colors...

Could this be an alternative to including sass artifacts in every shadow dom component stylesheet for small projects?

JoeCool
  • 907
  • 1
  • 11
  • 25
0
  1. Set application styles rule to SASS:
    Updating Custom components theme in run-time require use of @mixin therefore your application style rules should be SASS (not CSS). You can read here on how to config Angular-Cli with SASS: https://scotch.io/tutorials/using-sass-with-the-angular-cli

  2. Define @mixin for custom component:
    In each component that uses theme colors, create a @mixin in its .scss file. For this component extract all the color definition. And move them into the @mixin, like this:

// --- file: my-component_1.scss ---
@import '~@angular/material/theming'; // we need to add this so we could use Material functions
@mixin set-theme-component-1($theme)
{
  // Extract whichever individual palettes you need from the theme.
  $primary-palette: map-get($theme, primary);
  $accent-palette:  map-get($theme, accent);
  $warn-palette:    map-get($theme, warn);

  .component-container
  {
    background-color: mat-color($primary-palette); // use the mat-color function to extract the color from the palette
    border-color: mat-color($warn-palette);
  }
}

// Style rules that aren't theme/color related (size, font, etc)
.component-container
{
  width: 100%;
  ...
}
  1. Define a themes file:
    You will need to define a themes file (if you haven't done so already) and invoke the @mixins we defined in this file, like this:
// --- file: app.theme.scss ---
@import '~@angular/material/theming';
@include mat-core(); // include this only once in your code!!!

// each custom component that uses theme colors will be imported here - we need there @mixin
@import './some-path/some-folder/my-component_1'; // no need to specify .scss suffix

@mixin set-theme($theme) // define a new @mixin that will be invoked each time the theme is changed
{
  @include set-theme-component-1($theme); // invoke the mixin we defined for component_1
  // repeat this for each component that uses theme colors
}

// define your themes:
.theme-light
{
  $light-primary: mat-palette($mat-indigo);
  $light-accent:  mat-palette($mat-pink, A200, A100, A400);
  $light-warn:    mat-palette($mat-red);
  $light-theme:   mat-light-theme($light-primary, $light-accent, $light-warn);

  @include angular-material-theme($light-theme);
  @include set-theme($light-theme); // once the theme was set, invoke the mixin
}

.theme-dark
{
  $dark-primary:  mat-palette($mat-teal, A400);
  $dark-accent:   mat-palette($mat-grey, 800);
  $dark-warn:     mat-palette($mat-red, 700);
  $dark-theme:    mat-dark-theme($dark-primary, $dark-accent, $dark-warn);

  @include angular-material-theme($dark-theme);
  @include set-theme($dark-theme); // once the theme was set, invoke the mixin
}

That's that :-)

They are a few additional steps that you need to implement in order of live theming to work (they aren't related to custom components, so I will only go throw them quickly).
- Create a ThemeService that will save the current theme. This service will need to update the OverlayContainer (Angular material uses this container for background of modals like pop-ups and dropdown-lists).
- Add the theme class (theme-dark or any other) to one of the root elements in the DOM.
- Add the class mat-app-background to one of the root elements in the DOM. This will change the background color and font color according to the theme.
- For better software design - if you have a lot of themes, splitting the theme file may be a good idea for maintenance porpoises.

You can continue reading here: https://material.angular.io/guide/theming

or see there Github project: https://github.com/angular/material2/blob/master/src/lib/core/theming/_theming.scss

Gil Epshtain
  • 8,670
  • 7
  • 63
  • 89
  • This solution works, but you should be aware that by importing a component's .scss in global styles.scss you're making all component's style classes available globally to the app: this could be very messy if you're not very careful with class names. – Alessandro Prete Jan 05 '21 at 11:47
0

If you want to have a color change according to your theme you can simply define the same variable differently nested within each of the classes that define your themes. For example in my app themes are set by assigning a class to the outer container of my app. Therefore, nested within each of the "theme classes" I can assign the same variable to be a different value. So when the "theme class" assigned to the whole app changes, the value of my variable will change along with it:

.app-light-theme {
  @include angular-material-theme($theme);
  * {
     --my-variable-color: yellow;
    }
}

.app-dark-theme {
  @include angular-material-theme($altTheme);
  * {
    --my-variable-color: purple;
  }
}

Then in any css file in my app I can use my variable by using var():

background-color: var(--my-variable-color);

and the color will change depending on what theme the app is set to.

You can use theme's primary, accent and warn colors simply by setting --primary --accent and --warn variables in each of your themes.

Dcode22
  • 97
  • 4
0

For years i struggled with this.

Thanks to previous answer https://stackoverflow.com/a/54259083/1106835, here is an implementation for angular 14 slightly different applied directly in my theme (got a dark mode too):

@use '@angular/material' as mat;
// ...custom color palettes if necessary...

$light-primary: mat.define-palette($coolGray, 700);
$light-accent: mat.define-palette($indigo, 500);
$light-warn: mat.define-palette($rose, 500);

$theme-light: mat.define-light-theme($light-primary, $light-accent, $light-warn);

$dark-primary: mat.define-palette($fuchsia, 500);
$dark-accent: mat.define-palette($sky, 500);
$dark-warn: mat.define-palette($rose, 500);

$theme-dark: mat.define-dark-theme($dark-primary, $dark-accent, $dark-warn);

.theme-light {
  @include mat.all-component-themes($theme-light);
  --color-primary: #{mat.get-color-from-palette($light-primary)};
  --color-accent: #{mat.get-color-from-palette($light-accent)};
  --color-warn: #{mat.get-color-from-palette($light-warn)};
}

.theme-dark {
  @include mat.all-component-themes($theme-dark);
  --color-primary: #{mat.get-color-from-palette($dark-primary, 500)};
  --color-accent: #{mat.get-color-from-palette($dark-accent, 500)};
  --color-warn: #{mat.get-color-from-palette($dark-warn, 500)};
}

Make sure theme is applied

 <body class="theme-light" ...

Then you can use them in any scss file. In my case i used them to define some useful classes like:

.color {
  &--primary {
    background-color: var(--color-primary) !important;
  }

  &--accent {
    background-color: var(--color-accent) !important;
  }

  &--warn {
    background-color: var(--color-warn) !important;
  }
}

.bg-color {
  &--primary {
    background-color: var(--color-primary) !important;
  }

  &--accent {
    background-color: var(--color-accent) !important;
  }

  &--warn {
    background-color: var(--color-warn) !important;
  }
}
millenion
  • 1,218
  • 12
  • 16