0

Scenario
We followed this tutorial to provide our users a dark and a light theme.

Problem
The browser loads the expected css-File (dark or light). Nevertheless, the styles are not applied to our components. This happens in <head></head>:

<link rel="stylesheet" type="text/css" href="theme-dark.css">

or

<link rel="stylesheet" type="text/css" href="theme-light.css">

As you can see, the browser correctly switches the two themes.

Code
angular.json

"styles": [
          "projects/menu/src/styles.scss",
          {
            "input": "projects/menu/src/theming/theme-dark.scss",
            "bundleName": "theme-dark",
            "inject": false
          },
          {
            "input": "projects/menu/src/theming/theme-light.scss",
            "bundleName": "theme-light",
            "inject": false
          }
        ],

theme-service.ts

private _mainTheme$: BehaviorSubject<string> = new BehaviorSubject(
    'theme-light'
);
private _darkMode$: BehaviorSubject<boolean> = new BehaviorSubject(false);

darkMode$: Observable<boolean> = this._darkMode$.asObservable();

private _renderer: Renderer2;
private head: HTMLElement;
private themeLinks: HTMLElement[] = [];

theme$: Observable<[string, boolean]>;

constructor(
    rendererFactory: RendererFactory2,
    @Inject(DOCUMENT) document: Document
) {
    this.head = document.head;
    this._renderer = rendererFactory.createRenderer(null, null);
    this.theme$ = combineLatest([this._mainTheme$, this._darkMode$]);
    this.theme$.subscribe(async ([mainTheme, darkMode]) => {
        const cssExt = '.css';
        const cssFilename = darkMode
            ? 'theme-dark' + cssExt
            : mainTheme + cssExt;
        await this.loadCss(cssFilename);
        if (this.themeLinks.length == 2)
            this._renderer.removeChild(this.head, this.themeLinks.shift());
    });
}

setMainTheme(name: string) {
    this._mainTheme$.next(name);
}

setDarkMode(value: boolean) {
    this._darkMode$.next(value);
}

private async loadCss(filename: string) {
    return new Promise((resolve) => {
        const linkEl: HTMLElement = this._renderer.createElement('link');
        this._renderer.setAttribute(linkEl, 'rel', 'stylesheet');
        this._renderer.setAttribute(linkEl, 'type', 'text/css');
        this._renderer.setAttribute(linkEl, 'href', filename);
        this._renderer.setProperty(linkEl, 'onload', resolve);
        this._renderer.appendChild(this.head, linkEl);
        this.themeLinks = [...this.themeLinks, linkEl];
    });
}

theme-base.scss

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

@include mat-core();

@mixin theming($theme) {
  @include angular-material-theme($theme);
}

theme-dark.scss

@import '~@angular/material/theming';
@import './theme-base';

@include mat-core();

// color palette definitions

$primary: mat-palette($dark-md-mcgpaletteblack);
$accent: mat-palette($md-mcgpalettewhite);
$warn: mat-palette($dark-md-mcgpalettered);
$background:mat-palette($dark-md-mcgpalettebackground);
$confirm: mat-palette($dark-md-mcgpaletteorange);

$theme-dark: mat-dark-theme((color: (primary: $primary,
  accent: $accent,
  warn: $warn,
  background: $background,
  confirm: $confirm)));

theme-dark.scss - looks the same

In style.scss we import both themes:

styles.scss

@import 'projects/menu/src/theming/theme-dark.scss';
@import 'projects/menu/src/theming/theme-light.scss';

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

.theme-light {
  @include angular-material-theme($theme-light)
}

and then we use those variables:

styles.scss

body {
  background: mat-color($background, default);
}

h1 {
   color: mat-color($accent, default);
}

But currently we also import them in our components. I think this is one thing, that is wrong.
A dialog would look like this:

@import 'projects/menu/src/theming/theme-dark.scss';
@import 'projects/menu/src/theming/theme-light.scss';

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

.theme-light{
  @include angular-material-theme($theme-light)
}

.mat-button {
  color: mat-color($accent, default);
  background-color: mat-color($confirm, default);
}

What I tried:

  • Removing the import of both themes in every stylesheet. Colors are still influenced by the order of the theme import in style.scss. Dark theme last -> dark theme will be used and the other way round.
  • Using the same variables for the themes as in the tutorial. But then we had to change of all of the background-color: mat-color($confirm, default);
  • And so on. Spent lots of time...

We can't figure out how to put the missing parts together. Do we need to use mixins? Or are we missing setting the class dark-theme on the root component?

Thank you very much in advance.

1 Answer 1

1

We finally got it working.

Our mistakes:

  • @include mat-core(); must only be imported once (in theme-base.scss)
  • @import 'projects/menu/src/theming/theme-dark.scss'; and @import 'projects/menu/src/theming/theme-light.scss'; must only be imported once (in styles.scss). Importing them in all of the components lets bundle size explode.
  • Set theme-dark class directly in theme-dark.scss
  • Do not set classes theme-dark and theme-light in styles.scss or any other component theme
  • Create two wrapper elements for whole application: <div [ngClass]="{ 'theme-dark': darkMode$ | async }"> <div class="mat-app-background"> <router-outlet></router-outlet></div></div>
  • If components need to access the theme, use mixins (example shown below)
  • In theme-service.ts, use OverlayContainer to apply styles to material dialogs as well (as described here)

Mixin example:

settings-dialog.component.scss

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

@mixin settings-dialog-theme($theme) {
  .settings-dialog {
    @include mat-elevation(2);
    .mat-button {
      background-color: mat-color($confirm);
    }
  }
}

settings-dialog.component.html

<div mat-dialog-content class="settings-dialog"> Put your content here </div>

Create a component theme as described here
component-themes.scss

@import 'your-path/settings-dialog.component';
@mixin component-themes($theme) {
  @include settings-dialog-theme($theme);
}

Import it into your base theme file
theme-base.scss

@import '~@angular/material/theming';
@import './component-themes.scss';

@include mat-core();

@mixin theming($theme) {
    @include angular-material-theme($theme);
    @include component-themes($theme);
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.