Skip to content

Commit 9dac25b

Browse files
committed
chore: store dev-app dark/light theme state in localStorage
1 parent 581d1f2 commit 9dac25b

File tree

3 files changed

+94
-21
lines changed

3 files changed

+94
-21
lines changed

src/dev-app/dev-app/dev-app-layout.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ <h1>Angular Material Demos</h1>
3333
<button mat-icon-button (click)="toggleFullscreen()" title="Toggle fullscreen">
3434
<mat-icon>fullscreen</mat-icon>
3535
</button>
36-
<button mat-button (click)="toggleTheme()">{{dark ? 'Light' : 'Dark'}} theme</button>
36+
<button mat-button (click)="theme.toggleTheme()">{{theme.isDark ? 'Light' : 'Dark'}} theme</button>
3737
<button mat-button (click)="rippleOptions.disabled = !rippleOptions.disabled">
3838
{{rippleOptions.disabled ? 'Enable' : 'Disable'}} ripples
3939
</button>

src/dev-app/dev-app/dev-app-layout.ts

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@
77
*/
88

99
import {Directionality} from '@angular/cdk/bidi';
10-
import {OverlayContainer} from '@angular/cdk/overlay';
11-
import {ChangeDetectorRef, Component, ElementRef, Inject, ViewEncapsulation} from '@angular/core';
10+
import {
11+
ChangeDetectorRef,
12+
Component,
13+
ElementRef,
14+
Inject,
15+
OnInit,
16+
ViewEncapsulation
17+
} from '@angular/core';
1218
import {DevAppRippleOptions} from './ripple-options';
1319
import {DevAppDirectionality} from './dev-app-directionality';
20+
import {DevAppTheme} from './dev-app-theme';
1421

1522
/** Root component for the dev-app demos. */
1623
@Component({
@@ -19,8 +26,7 @@ import {DevAppDirectionality} from './dev-app-directionality';
1926
styleUrls: ['dev-app-layout.css'],
2027
encapsulation: ViewEncapsulation.None,
2128
})
22-
export class DevAppLayout {
23-
dark = false;
29+
export class DevAppLayout implements OnInit {
2430
navItems = [
2531
{name: 'Examples', route: '/examples'},
2632
{name: 'Autocomplete', route: '/autocomplete'},
@@ -85,12 +91,16 @@ export class DevAppLayout {
8591
];
8692

8793
constructor(
88-
private _element: ElementRef<HTMLElement>, private _overlayContainer: OverlayContainer,
89-
public rippleOptions: DevAppRippleOptions,
94+
private _element: ElementRef<HTMLElement>,
95+
public rippleOptions: DevAppRippleOptions, public theme: DevAppTheme,
9096
@Inject(Directionality) public dir: DevAppDirectionality, cdr: ChangeDetectorRef) {
9197
dir.change.subscribe(() => cdr.markForCheck());
9298
}
9399

100+
ngOnInit(): void {
101+
this.theme.elementToTheme = this._element.nativeElement;
102+
}
103+
94104
toggleFullscreen() {
95105
// Cast to `any`, because the typings don't include the browser-prefixed methods.
96106
const elem = this._element.nativeElement.querySelector('.demo-content') as any;
@@ -104,18 +114,4 @@ export class DevAppLayout {
104114
elem.msRequestFullScreen();
105115
}
106116
}
107-
108-
toggleTheme() {
109-
const darkThemeClass = 'demo-unicorn-dark-theme';
110-
111-
this.dark = !this.dark;
112-
113-
if (this.dark) {
114-
this._element.nativeElement.classList.add(darkThemeClass);
115-
this._overlayContainer.getContainerElement().classList.add(darkThemeClass);
116-
} else {
117-
this._element.nativeElement.classList.remove(darkThemeClass);
118-
this._overlayContainer.getContainerElement().classList.remove(darkThemeClass);
119-
}
120-
}
121117
}

src/dev-app/dev-app/dev-app-theme.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Injectable} from '@angular/core';
10+
import {OverlayContainer} from '@angular/cdk/overlay';
11+
12+
const isDarkThemeKey = 'isDarkTheme';
13+
14+
@Injectable({providedIn: 'root'})
15+
export class DevAppTheme {
16+
readonly darkThemeClass = 'demo-unicorn-dark-theme';
17+
private _elementToTheme: HTMLElement;
18+
private _isDark = false;
19+
20+
constructor(private _overlayContainer: OverlayContainer) {
21+
let isDark: string | null;
22+
try {
23+
isDark = localStorage.getItem(isDarkThemeKey);
24+
if (isDark != null) {
25+
this._isDark = isDark === 'true';
26+
}
27+
} catch (error) {
28+
console.error(`Failed to read ${isDarkThemeKey} from localStorage: `, error);
29+
}
30+
}
31+
32+
get elementToTheme(): HTMLElement {
33+
return this._elementToTheme;
34+
}
35+
36+
set elementToTheme(value: HTMLElement) {
37+
if (value != null) {
38+
this._elementToTheme = value;
39+
this.isDark ? this.applyDarkTheme() : this.applyLightTheme();
40+
}
41+
}
42+
43+
get isDark(): boolean {
44+
return this._isDark;
45+
}
46+
47+
set isDark(value: boolean) {
48+
if (value != null) {
49+
this._isDark = value;
50+
try {
51+
localStorage.setItem(isDarkThemeKey, String(value));
52+
} catch (error) {
53+
console.error(`Failed to write ${isDarkThemeKey} to localStorage: `, error);
54+
}
55+
}
56+
}
57+
58+
toggleTheme() {
59+
this.isDark = !this._isDark;
60+
61+
if (this._isDark) {
62+
this.applyDarkTheme();
63+
} else {
64+
this.applyLightTheme();
65+
}
66+
}
67+
68+
applyDarkTheme() {
69+
this.elementToTheme.classList.add(this.darkThemeClass);
70+
this._overlayContainer.getContainerElement().classList.add(this.darkThemeClass);
71+
}
72+
73+
applyLightTheme() {
74+
this.elementToTheme.classList.remove(this.darkThemeClass);
75+
this._overlayContainer.getContainerElement().classList.remove(this.darkThemeClass);
76+
}
77+
}

0 commit comments

Comments
 (0)