64

I'm building an app with multiple theme with angular material design 2. I created multiple theme and it's working really great. Using this guideline : Angular Material design theme

But the problem is that if user select "green theme" for example. Then I want to display his/her name in green and so. But how can I get the currently selected theme in this case "green" in my component style and then use that primary variable in my user name class to change its color

Mel
  • 5,837
  • 10
  • 37
  • 42
Rahul
  • 929
  • 2
  • 8
  • 17
  • did you see [this, Theming your custom components](https://material.angular.io/guide/theming-your-components). – Madhu Ranjan Jul 13 '17 at 19:19
  • 2
    yes i have seen it. but it didn't worked for me or i didn't understand it properly. that's why i came here – Rahul Jul 13 '17 at 19:22
  • Can you show what you've already tried? – Will Howell Jul 15 '17 at 22:19
  • I tried adding different theme let's say in this case.. `Indigo and green` It's working great on all the material design specfic stuff . Like button , toggle , card etc. But i want to change `

    ` text based on my app current theme. like if user choosed green theme then `h1` text color should change to green and so..

    – Rahul Jul 17 '17 at 10:12
  • 1
    This is the post where i got my answer well explained. Wow :) [https://medium.com/@tomastrajan/the-complete-guide-to-angular-material-themes-4d165a9d24d1](https://medium.com/@tomastrajan/the-complete-guide-to-angular-material-themes-4d165a9d24d1) – Rahul Jul 20 '17 at 09:33
  • see my answer to this question... [It's Long,... but Complete..](https://stackoverflow.com/questions/53922711/how-to-change-the-global-variable-inside-a-scss-mixin) – Pradeep Jan 16 '19 at 17:27

8 Answers8

58

I'm not sure if this is the "correct" way to do it, but it works, so I'm running with it for now. I'll adapt if there's a better way. My goal was to be able to style non-Material elements (such as standard DIVs, SPANs, etc) with different colors depending on which Material theme was currently applied. It took a combination of Material 2 and Angular 2 elements to make it all work.

Here is what I did: My custom theme file looks like this:

@import '~@angular/material/_theming.scss';

@include mat-core();

// default theme:
$primary: mat-palette($mat-blue,800);
$accent: mat-palette($mat-teal);
$theme: mat-light-theme($primary, $accent);

@include angular-material-theme($theme);

// "dark" theme
$dark-p: mat-palette($mat-blue-grey, 500);
$dark-a: mat-palette($mat-blue-grey,900);
$dark-t: mat-dark-theme($dark-p, $dark-a);

.darkTheme {
  @include angular-material-theme($dark-t);
}

A snippet from my application scss file:

@import '../../themes/main-theme';  //  <-- the theme file shown above

//default palette forground/background:
$light-foreground-palette: map-get($theme, foreground);
$light-background-palette: map-get($theme, background);

//dark palette forground/background:
$dark-foreground-palette: map-get($dark-t, foreground);
$dark-background-palette: map-get($dark-t, background);

.light-colors{
    background-color : mat-color($primary, default);
    color: mat-color($light-foreground-palette, text);
}
.dark-colors{
    background-color : mat-color($dark-p, default);
    color: mat-color($dark-foreground-palette, text);
}

In my "theme" service (although you could do it in any service, as long as it's available globally, or at least anywhere you need it), I defined a simple boolean variable isDarkTheme. I use that to control display depending on whether the user has selected the "dark" theme.

Then wherever I need to, I use ngClass to apply classes dynamically, depending on the value of the global isDarkTheme variable:

<div [ngClass]="{'light-colors' : !svc.isDarkTheme,'dark-colors' : svc.isDarkTheme}">
...my content...
</div>

I have a div wrapping my entire application using the same ngClass approach to either apply the darkTheme class or not depending on the value of the isDarkTheme variable. This take care of all Material-aware elements in my entire application in one shot, and I just use the light-colors and dark-colors on the specific non-Material elements where i need to. I could probably combine these, but for now I'm leaving things as-is.

For completeness, here are the lists of the elements you can get from the different palettes: From the "primary" palette ($primary and $dark-p in my code above):

  • default
  • lighter
  • darker

You can also get these same three color values for the $accent and $warn palettes.

From the "foreground" palette ($light-foreground-palette and $dark-foreground-palette in my code above):

  • base
  • divider
  • dividers
  • disabled
  • disabled-button
  • disabled-text
  • hint-text
  • secondary-text
  • icon
  • icons
  • text
  • slider-off
  • slider-off-active

From the "background" palette ($light-background-palette and $dark-background-palette in my code above):

  • status-bar
  • app-bar
  • background
  • hover
  • card
  • dialog
  • disabled-button
  • raised-button
  • focused-button
  • selected-button
  • selected-disabled-button
  • disabled-button-toggle

Here are the sources I used to put this together:

I'll freely admit to only understanding about 80% of what's going on here, so if there's a better way, please let me know...

TimTheEnchanter
  • 3,370
  • 1
  • 26
  • 47
  • I also had to import `@import '~@angular/material/theming';` in the component scss file – Mel Dec 18 '17 at 13:53
41

I know this is super late to the party, but didn't want to pull a DenverCoder9 after finally figuring out a clean way to do this.

First, go to this link on the angular material2 github and figure out what color palettes your theme is using. (That link points to version 6, so make sure you change the tag to whatever version you're using.)

Then create a variables.scss file in your project somewhere to store the palette references for your theme (the example below uses the palettes for the indigo-pink theme):

/* variables.scss */
@import "~@angular/material/theming";

$primaryPalette: mat-palette($mat-indigo);
$accentPalette:  mat-palette($mat-pink, A200, A100, A400);

// the default warn palette is red, so use that if the theme doesn't define one
$warnPalette:    mat-palette($mat-red);

You can then include the variables.scss file into your style sheets and use mat-color(<palette>) to get the color for you classes.

/* my-component.scss */
@import "~@angular/material/theming";
@import 'variables.scss';

.my-primary-text {
    color: mat-color($primaryPalette);
}

.my-accent-text {
    color: mat-color($accentPalette);
}

Using this method, you can still use the pre-built themes. It would probably be even cleaner just to re-create the theme using their published documentation, but for now I'm happy with this.

Hopefully that saves the next guy a lot of pain and suffering down the road.

UPDATE: Redefine Default Theme to Prevent OhGodTheyChangedColorsThisRelease

So who knows when angular material might change their theme colors, so its probably a good idea to just re-create the default them.

So building on the previous part of the post, add the mat-light-theme/mat-dark-theme mixins to re-define the new theme.

/* variables.scss */
@import "~@angular/material/theming";

$primaryPalette: mat-palette($mat-indigo);
$accentPalette:  mat-palette($mat-pink, A200, A100, A400);

// the default warn palette is red, so use that if the theme doesn't define one
$warnPalette:    mat-palette($mat-red);

// re-define the indigo-pink theme
$defaultTheme: mat-light-theme($primaryPalette, $accentPalette, $warnPalette);

Then in your master root styles.scss for your app,

/* styles.scss */
@import '~@angular/material/theming';
@import './scss/variables.scss';

@include angular-material-theme($defaultTheme);

Make sure you drop the <link href=> in your index.html for the default stylesheet.

Great! Now if you update angular material and they changed the colors, your good!, And you can update the colors across the app with just mucking with the palettes in variables.scss.

glenatron
  • 11,018
  • 13
  • 64
  • 112
tazer84
  • 1,743
  • 12
  • 11
  • 3
    In case you want to use the same color from a palette (e.g. primary color) in several places and want to take advantage of css custom properties (variables), don't forget to use the sass interpolation syntax: `--primary-color: #{mat-color($primaryPalette)};` – Fabian Jun 04 '18 at 08:05
  • 2
    In this case, the size of the output bundle is too large due to the inclusion of styles. How to deal with this? – Interloper Jul 11 '19 at 07:39
  • 1
    after hours of trying to figure out how to use the existing theme colors. this helped me. thank you buddy. – Plixxer Sep 01 '19 at 17:10
  • This does not work, variables `SassError: Can't find stylesheet to import.` – Damian Kowalski Nov 15 '22 at 18:27
6

You can use class="mat-primary" and class="mat-accent" on HTML elements to get the primary and accent colours of your theme.

Niels Meima
  • 119
  • 1
  • 29
    It's working in material design element like button etc. it's kind of alternate of [color] attr. but It's not working on any html element `

    Lorem ipsum dolor sit amet.

    ` it's not changing `

    ` color. is there a way that i can simply get color in my css and style it for eg.. `h3{ color : @mat-primary; }` Like this

    – Rahul Jul 17 '17 at 10:07
  • i am also looking for answer to this. i want to set the selected tab color on my page to be primary color of current theme. less code . so when user changes theme . i dont have to do anything – Mustafa Oct 08 '18 at 11:22
4

You can generate CSS classes and CSS variables from material theme. Basically, you need this function:

@use '../palette';

@mixin generateColors($prefix, $palette) {
  $colors-map: ();

  @each $key, $value in $palette {
    @if $key !=contrast {
      .app-#{$prefix}-#{$key} {
        color: map-get($palette, $key);
      }

    $map: ();
    $map: map-merge($map, ($key: $value));
    $colors-map: map-merge($colors-map, $map);
    }
  }

  :root {
    @each $key, $value in $colors-map {
      --app-#{$prefix}-#{$key}: #{$value};
    }
  }
}

@mixin generate-material-classes {
   @include generateColors(primary, $youtube-primary);
   @include generateColors(accent, $youtube-accent);
   @include generateColors(warning, $youtube-warning);
}

This would generate CSS classes and CSS variables so you can use like below:

 <p class="app-primary-50 title"> </p>

 // or using CSS variables
 p {
   title: var(--app-primary-50);
 }

In your case on each theme, you should call the below function.

 .green-theme {
    @include generate-material-classes();
}

You can also check the blog about this topic: LINK.

Vugar Abdullayev
  • 1,852
  • 3
  • 21
  • 46
1

I've created a simple mixin to solve this issue.

$theme: mat-light-theme((color:(primary: $primary,accent: $accent,warn: $warn)));  
$pv-theme: mat-light-theme((color:(primary: $pv-primary,accent: $pv-accent,warn: $pv-warn)));  
$avg-theme: mat-light-theme((color:(primary: $avg-primary,accent: $avg-accent,warn: $avg-warn)));  

@include angular-material-theme($theme);

.avg {
  @include angular-material-color($avg-theme);
}

.pv {
  @include angular-material-color($pv-theme);
}

  • Create a mixin:
@mixin theme($property: null, $key: null) {
  & {
    #{$property}: mat-color(map_get($theme, $key));
  }

  .avg & {
    #{$property}: mat-color(map_get($avg-theme, $key));
  }

  .pv & {
    #{$property}: mat-color(map_get($pv-theme, $key));
  }
}

  • Use it like so:
footer .container {
  @include theme(background-color, primary);
}
Vivek Jain
  • 2,730
  • 6
  • 12
  • 27
Bart
  • 11
  • 2
  • 1
    I would also add that to make it work inside components with styles encapsulation you should add `:host-context(.#{$your-theme-class}) &` alongside with `.#{$your-theme-class} &`. – Ivan Frolov Apr 13 '21 at 13:52
  • How does this work with the major changes in Angular 12? I tried to implement it but it does not work anymore. – CptDayDreamer Aug 31 '21 at 15:49
0

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;
}

HTML:

<label class="example">Name</label>
Saurabh Palatkar
  • 3,242
  • 9
  • 48
  • 107
0

In my angular project Im creating the css classes by my own based on the given Material Theme. Therefor you can use these classes all over your html styling - its a more decoupled way. In the first step I've created the initial css classes (im using scss) for the several theming modes like dark or light mode:

.default-theme {
    &--light {
      @include angular-material-theme($default--light);
      @include create-css-class-colors($default--light, "light");
    }
    &--dark {
      @include angular-material-theme($default--dark);
      @include create-css-class-colors($default--dark, "dark");
    }
}

This setup could be done in the style.scss file.

After you have done the initial setup you have to implement the mixin in the code example above for "create-css-class-colors($default--light, "light")" in an extra file or leave it all in one large file for testing:

@mixin create-css-class-colors($colors, $mode) {
  .text-color,
  .font-color {
    @include text-color-list($colors, $mode);
  }
  .background-color {
    @include background-color-list($colors, $mode);
  }
}

The mixin above has always a css-class name and an additional function for creating the responsible css-classes like background-color--primary or font-color--primary (naming is up to you). The logic of these functions is like the following:

@mixin background-color-list($map, $mode) {
  $color-list: get-colors($map, $mode);

  @each $color in $color-list {
    $color-name: map-get($color, name);

    &--#{$color-name} {
      background-color: map-get($color, color) !important;
    }
  }
}

One last step is needed to complete all the setup. Besides of Angular Material you could define further colors you wanna use in your project. To do so you need another function "get-colors($map, $mode)":

@function get-colors($map, $mode) {
  $primary-color: (
    name: "primary",
    color: getSpecificThemeColor($map, primary),
  );

  $accent-color: (
    name: "accent",
    color: getSpecificThemeColor($map, accent),
  );

$info: (
    name: "info",
    color: #00bcd4,
  );

$color-list: $primary-color, 
    $accent-color,
    $info;
@return $color-list;

}

To get the specific theme color of the angular theme you have to implement one last function "getSpecificThemeColor($theme, $colorType)":

@function getSpecificThemeColor($theme, $colorType) {

    @if map-has-key($theme, color) {
        $colors: map-get($theme, color);
        $color: map-get($colors, $colorType);

        @return mat-color($color);
    }
    @else {
        $color: map-get($theme, $colorType);

        @return mat-color($color);
    }     
}

Now you are able to use the css classes to style your none-angular-material-components. If you wanna set the background-color of a div you can now use the generated css class "background-color--primary" or if you wanna set the font-color to accent you can use font-color--accent etc. but with this setup it is possible to generate your own color css classes as well like info.

braunpa
  • 106
  • 1
  • 2
-2

In order to get any color from theme which is currently applied e.g. primary try this, where $theme is your theme variable name

$primary: map-get($theme, primary);
Nadeem
  • 419
  • 4
  • 13