ServiceUpdated in v15

WrTheme

Switch between light, dark and auto, and keep the choice.

Installation

Import the class, and @use the entry point in your global styles: without it the component renders unstyled.

import { WrTheme, wrThemePrePaintScript } from 'ngwr/theme';

Usage

Needs no provider: inject it where the toggle lives.

private readonly theme = inject(WrTheme);

this.theme.set('dark');
this.theme.toggle();

protected readonly resolved = this.theme.resolved; // Signal<'light' | 'dark'>
protected readonly mode = this.theme.mode;         // Signal<'light' | 'dark' | 'auto'>
Mode: auto · Resolved: light

Configuration

withTheme() in provideWr() sets defaultMode and storageKey.

import { provideWr } from 'ngwr';
import { withTheme } from 'ngwr/theme';

bootstrapApplication(AppComponent, {
  providers: [
    // Defaults: 'auto' and 'wr-theme'; storageKey: null stops persisting.
    provideWr(withTheme({ defaultMode: 'dark', storageKey: 'app-theme' })),
  ],
});

SSR: no flash of the wrong theme

Put this script in <head>, above the stylesheets, so a stored choice applies before the first paint.

<script>(function(){try{var m=null,r=null;try{r=window.localStorage.getItem("wr-theme");}catch(e){}if(r!==null){try{var p=JSON.parse(r);if(p!==null&&typeof p==="object"&&"v" in p){if(p.e===undefined||p.e>=Date.now())m=p.v;}else{m=p;}}catch(e){m=r;}}if(m!=="light"&&m!=="dark"&&m!=="auto")m="auto";if(m==="light"||m==="dark")document.documentElement.style.colorScheme=m;}catch(e){}})();</script>
import { wrThemePrePaintScript } from 'ngwr/theme';

// Pass the values you gave withTheme() and withStorage():
wrThemePrePaintScript({ storageKey: 'app-theme', defaultMode: 'dark', storagePrefix: 'myapp:' });

// It returns the source without <script>, so a strict CSP can hash it:
const hash = createHash('sha256').update(wrThemePrePaintScript()).digest('base64');

Gotchas

  • Without withTheme(), a stored choice applies only once something injects WrTheme.
  • withTheme() in a route's providers is ignored.
  • With two Angular apps on one page, only one should inject WrTheme.

API

NameDescriptionTypeDefault
modeThe chosen mode: light, dark or auto.Signal<WrThemeMode>—
resolvedThe scheme in effect; auto follows the OS.Signal<'light' | 'dark'>—
set(mode)Switch to a specific mode.(m: WrThemeMode) => void—
toggle()Flip between light and dark (skips auto).() => void—
WR_THEME_CONFIGHolds defaultMode and storageKey. Set it with withTheme().InjectionToken<WrThemeConfig>—
withTheme(config?)The provideWr() feature: sets WR_THEME_CONFIG and creates WrTheme at boot.(config?: Partial<WrThemeConfig>) => WrFeature<'theme'>—
wrThemePrePaintScript(options?)Source of the script that applies a stored choice before the first paint. Takes the withTheme() and withStorage() values.(options?: WrThemePrePaintOptions) => string—

Dependencies

Built onWrStorage

See also