The Paginator application includes a built-in theme system that supports light and dark modes with persistent user preferences. This guide explains how the theming system works and how to customize it.
Theme Service Overview
The ThemeService (src/app/core/services/theme.service.ts:1) is a singleton service that manages theme state across the application:
@ Injectable ({
providedIn: 'root'
})
export class ThemeService {
isDarkMode = false ;
isBrowser : boolean ;
constructor (@ Inject ( PLATFORM_ID ) private platformId : Object ) {
this . isBrowser = isPlatformBrowser ( this . platformId );
}
toggleDarkMode () : void {
this . isDarkMode = ! this . isDarkMode ;
if ( this . isBrowser ) {
document . body . classList . toggle ( 'dark-mode' , this . isDarkMode );
localStorage . setItem ( 'darkMode' , this . isDarkMode . toString ());
}
}
initializeTheme () : void {
if ( this . isBrowser ) {
const savedMode = localStorage . getItem ( 'darkMode' ) === 'true' ;
this . isDarkMode = savedMode ;
document . body . classList . toggle ( 'dark-mode' , this . isDarkMode );
}
}
get currentMode () : boolean {
return this . isDarkMode ;
}
}
Key Features
SSR-Compatible : Uses PLATFORM_ID to detect browser environment
Persistent Storage : Saves preference to localStorage
CSS Class-Based : Uses dark-mode class on body element
Reactive : Components can bind to currentMode property
Using the Theme Service
Inject the Service
Inject ThemeService into your component (home.component.ts:23): export class HomeComponent implements OnInit {
constructor (
private router : Router ,
private route : ActivatedRoute ,
private locationService : LocationsService ,
public themeService : ThemeService // Make public for template access
) {
this . themeService . initializeTheme ();
}
}
Make the service public to access it directly in templates.
Initialize Theme on App Start
Call initializeTheme() in your component constructor (home.component.ts:29): constructor ( public themeService : ThemeService ) {
this . themeService . initializeTheme ();
}
This loads the saved theme preference from localStorage.
Apply Theme Class to Elements
Use the currentMode property to conditionally apply the theme class (home.component.html:1): < div [class.dark-mode] = "themeService.currentMode" class = "main w-100 vh-100" >
<!-- Your content -->
</ div >
Add Theme Toggle Button
Create a button that calls toggleDarkMode(): < button (click) = "themeService.toggleDarkMode()" class = "btn btn-secondary" >
{{ themeService.currentMode ? 'Light Mode' : 'Dark Mode' }}
</ button >
How Theme Switching Works
The toggleDarkMode() method (theme.service.ts:16) performs three actions:
toggleDarkMode (): void {
// 1. Toggle the internal state
this . isDarkMode = ! this . isDarkMode ;
if ( this . isBrowser ) {
// 2. Update the CSS class on body
document . body . classList . toggle ( 'dark-mode' , this . isDarkMode );
// 3. Save preference to localStorage
localStorage . setItem ( 'darkMode' , this . isDarkMode . toString ());
}
}
Step-by-Step Flow
State Update : Toggles isDarkMode boolean
CSS Update : Adds/removes dark-mode class on <body>
Persistence : Saves preference to localStorage as string
Theme Initialization
The initializeTheme() method (theme.service.ts:25) runs on app startup:
initializeTheme (): void {
if ( this . isBrowser ) {
// Read saved preference
const savedMode = localStorage . getItem ( 'darkMode' ) === 'true' ;
// Update internal state
this . isDarkMode = savedMode ;
// Apply CSS class
document . body . classList . toggle ( 'dark-mode' , this . isDarkMode );
}
}
The localStorage value is stored as a string, so we check if it equals 'true'.
SSR Compatibility
The service uses isPlatformBrowser to ensure browser-specific code only runs in the browser:
import { isPlatformBrowser } from '@angular/common' ;
import { Inject , PLATFORM_ID } from '@angular/core' ;
export class ThemeService {
isBrowser : boolean ;
constructor (@ Inject ( PLATFORM_ID ) private platformId : Object ) {
this . isBrowser = isPlatformBrowser ( this . platformId );
}
toggleDarkMode () : void {
if ( this . isBrowser ) { // Only run in browser
// Access localStorage and DOM
}
}
}
Always wrap localStorage and document access in if (this.isBrowser) checks to prevent SSR errors.
CSS Implementation
The theme system uses a CSS class-based approach. Define styles for the dark-mode class:
// Default (light mode) styles
.main {
background-color : #ffffff ;
color : #000000 ;
}
// Dark mode overrides
.dark-mode .main {
background-color : #1a1a1a ;
color : #ffffff ;
}
// Or use the :has selector
body .dark-mode {
--bg-color : #1a1a1a ;
--text-color : #ffffff ;
}
Accessing Current Theme
Use the currentMode getter (theme.service.ts:33) to read the current theme state:
get currentMode (): boolean {
return this . isDarkMode ;
}
In Templates
<!-- Conditional rendering -->
< span *ngIf = "themeService.currentMode" > Dark Mode Active </ span >
<!-- Conditional classes -->
< div [class.dark-mode] = "themeService.currentMode" >
Content
</ div >
<!-- Conditional text -->
< button > {{ themeService.currentMode ? 'Light' : 'Dark' }} </ button >
In Components
export class MyComponent {
constructor ( private themeService : ThemeService ) {}
isDark () : boolean {
return this . themeService . currentMode ;
}
}
LocalStorage Key
The theme preference is stored under the darkMode key:
localStorage . setItem ( 'darkMode' , 'true' ); // Dark mode enabled
localStorage . setItem ( 'darkMode' , 'false' ); // Light mode enabled
You can inspect this in browser DevTools:
Open DevTools (F12)
Go to Application tab
Navigate to Local Storage
Look for the darkMode key
Example Implementation
Here’s a complete example of using the theme service in a component:
import { Component , OnInit } from '@angular/core' ;
import { ThemeService } from './services/theme.service' ;
@ Component ({
selector: 'app-root' ,
template: `
<div [class.dark-mode]="themeService.currentMode">
<header>
<h1>My App</h1>
<button (click)="themeService.toggleDarkMode()">
{{ themeService.currentMode ? '☀️' : '🌙' }}
</button>
</header>
<main>
<router-outlet></router-outlet>
</main>
</div>
`
})
export class AppComponent implements OnInit {
constructor ( public themeService : ThemeService ) {}
ngOnInit () : void {
// Initialize theme from localStorage
this . themeService . initializeTheme ();
}
}
Best Practices
Call initializeTheme() in your root component or app initializer to prevent flash of unstyled content: constructor ( public themeService : ThemeService ) {
this . themeService . initializeTheme ();
}
Make Service Public for Templates
If accessing the service in templates, make it public: constructor ( public themeService : ThemeService ) {}
Use CSS Classes, Not Inline Styles
Apply themes through CSS classes for better maintainability: < div [class.dark-mode] = "themeService.currentMode" >
Always Check Browser Platform
Show the current theme state in your toggle button: < button (click) = "themeService.toggleDarkMode()" >
{{ themeService.currentMode ? 'Switch to Light' : 'Switch to Dark' }}
</ button >
Advanced Customization
CSS Custom Properties
Use CSS variables for more flexible theming:
:root {
--bg-color : #ffffff ;
--text-color : #000000 ;
--border-color : #e0e0e0 ;
}
body .dark-mode {
--bg-color : #1a1a1a ;
--text-color : #ffffff ;
--border-color : #404040 ;
}
.main {
background-color : var ( --bg-color );
color : var ( --text-color );
border : 1 px solid var ( --border-color );
}
Multiple Themes
Extend the service to support multiple themes:
export type Theme = 'light' | 'dark' | 'blue' | 'green' ;
export class ThemeService {
currentTheme : Theme = 'light' ;
setTheme ( theme : Theme ) : void {
document . body . className = theme ;
localStorage . setItem ( 'theme' , theme );
}
}
System Preference Detection
Detect user’s system theme preference:
initializeTheme (): void {
if ( this . isBrowser ) {
const savedMode = localStorage . getItem ( 'darkMode' );
if ( savedMode !== null ) {
this . isDarkMode = savedMode === 'true' ;
} else {
// Use system preference if no saved preference
this . isDarkMode = window . matchMedia ( '(prefers-color-scheme: dark)' ). matches ;
}
document . body . classList . toggle ( 'dark-mode' , this . isDarkMode );
}
}
Next Steps
Component Architecture Learn about the app’s component structure
Services Explore other services in the application