Skip to content

Commit 408342c

Browse files
committed
Signal-Migration: ThemeService and TitleService
Consumers (AppComponent & SidNavButtonsCompoent) now use computed() values rather than subscribing to observables from these Services.
1 parent 2f020c4 commit 408342c

8 files changed

Lines changed: 50 additions & 56 deletions

File tree

src/app/app.component.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
<div class="title-container">
99
<div class="tag-line">
1010
<div class="tag-title">
11-
{{ title || defaultTitle }}
11+
{{ title() || defaultTitle }}
1212
</div>
13-
<div class="tag-subtitle" *ngIf="subtitle">
14-
{{ subtitle }}
13+
<div class="tag-subtitle" *ngIf="subtitle()">
14+
{{ subtitle() }}
1515
</div>
1616
</div>
1717
<div class="dummy"></div>

src/app/app.component.spec.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
2-
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2+
import { CUSTOM_ELEMENTS_SCHEMA, signal } from '@angular/core';
33
import { provideRouter } from '@angular/router';
44
import { AppComponent } from './app.component';
55
import { MatToolbarModule } from '@angular/material/toolbar';
66
import { MatIconModule } from '@angular/material/icon';
77
import { MatSidenavModule } from '@angular/material/sidenav';
88
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
9-
import { of } from 'rxjs';
109
import { ThemeService } from './service/theme.service';
1110
import { TitleService } from './service/title.service';
1211

1312
class MockThemeService {
13+
theme = signal<string>('light');
1414
initTheme() {}
1515
getTheme() {
16-
return 'light';
16+
return this.theme();
17+
}
18+
setTheme(theme: string) {
19+
this.theme.set(theme);
20+
}
21+
toggleTheme() {
22+
this.theme.set(this.theme() === 'light' ? 'dark' : 'light');
1723
}
18-
setTheme(theme: string) {}
1924
}
2025

2126
class MockTitleService {
22-
titleInfo$ = of({
27+
titleInfo = signal<{ dimension?: string; level?: number } | null>({
2328
dimension: 'Test Title',
24-
level: '1',
29+
level: 1,
2530
});
2631
}
2732

src/app/app.component.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { Component, OnInit, OnDestroy } from '@angular/core';
2-
import { Subject, takeUntil } from 'rxjs';
1+
import { Component, OnInit, computed } from '@angular/core';
32
import { ThemeService } from './service/theme.service';
43
import { TitleService } from './service/title.service';
54
import { SidenavButtonsComponent } from './component/sidenav-buttons/sidenav-buttons.component';
@@ -28,14 +27,16 @@ import { MatToolbarModule } from '@angular/material/toolbar';
2827
RouterOutlet,
2928
],
3029
})
31-
export class AppComponent implements OnInit, OnDestroy {
32-
title = '';
30+
export class AppComponent implements OnInit {
3331
defaultTitle = '';
34-
subtitle = '';
3532
menuIsOpen: boolean = true;
3633
sidenavWidth: string = '250px';
3734

38-
private destroy$ = new Subject<void>();
35+
readonly title = computed(() => this.titleService.titleInfo()?.dimension || '');
36+
readonly subtitle = computed(() => {
37+
const info = this.titleService.titleInfo();
38+
return info?.level ? 'Level ' + info.level : '';
39+
});
3940

4041
constructor(
4142
private themeService: ThemeService,
@@ -54,17 +55,6 @@ export class AppComponent implements OnInit, OnDestroy {
5455
} else {
5556
this.sidenavWidth = '250px';
5657
}
57-
58-
// Subscribe to title changes
59-
this.titleService.titleInfo$.pipe(takeUntil(this.destroy$)).subscribe(titleInfo => {
60-
this.title = titleInfo?.dimension || '';
61-
this.subtitle = titleInfo?.level ? 'Level ' + titleInfo?.level : '';
62-
});
63-
}
64-
65-
ngOnDestroy(): void {
66-
this.destroy$.next();
67-
this.destroy$.complete();
6858
}
6959

7060
toggleMenu(): void {

src/app/component/sidenav-buttons/sidenav-buttons.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ <h3 matListItemTitle>GitHub</h3>
2020
<mat-list-item (click)="toggleTheme()" style="cursor: pointer">
2121
<span matListItemIcon>
2222
<mat-icon color="primary">
23-
{{ isNightMode ? 'light_mode' : 'dark_mode' }}
23+
{{ isNightMode() ? 'light_mode' : 'dark_mode' }}
2424
</mat-icon>
2525
</span>
2626
<h3 matListItemTitle>
27-
{{ isNightMode ? 'Switch to Light Mode' : 'Switch to Dark Mode' }}
27+
{{ isNightMode() ? 'Switch to Light Mode' : 'Switch to Dark Mode' }}
2828
</h3>
2929
</mat-list-item>
3030
</mat-list>

src/app/component/sidenav-buttons/sidenav-buttons.component.spec.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component } from '@angular/core';
1+
import { Component, signal } from '@angular/core';
22
import { ComponentFixture, TestBed } from '@angular/core/testing';
33
import { provideRouter } from '@angular/router';
44
import { HttpClientTestingModule } from '@angular/common/http/testing';
@@ -7,11 +7,17 @@ import { SidenavButtonsComponent } from './sidenav-buttons.component';
77
import { ThemeService } from '../../service/theme.service';
88

99
class MockThemeService {
10+
theme = signal<string>('light');
1011
initTheme() {}
1112
getTheme() {
12-
return 'light';
13+
return this.theme();
14+
}
15+
setTheme(theme: string) {
16+
this.theme.set(theme);
17+
}
18+
toggleTheme() {
19+
this.theme.set(this.theme() === 'light' ? 'dark' : 'light');
1320
}
14-
setTheme(theme: string) {}
1521
}
1622

1723
describe('SidenavButtonsComponent', () => {

src/app/component/sidenav-buttons/sidenav-buttons.component.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, OnInit } from '@angular/core';
1+
import { Component, computed } from '@angular/core';
22
import { MatIconRegistry, MatIconModule } from '@angular/material/icon';
33
import { DomSanitizer } from '@angular/platform-browser';
44
import { GITHUB_SVG } from '../../../assets/svg_icons';
@@ -15,7 +15,7 @@ import { MatListModule } from '@angular/material/list';
1515
standalone: true,
1616
imports: [MatListModule, NgFor, RouterLink, MatIconModule, MatDividerModule],
1717
})
18-
export class SidenavButtonsComponent implements OnInit {
18+
export class SidenavButtonsComponent {
1919
Options: string[] = [
2020
'Overview',
2121
'Matrix',
@@ -53,7 +53,7 @@ export class SidenavButtonsComponent implements OnInit {
5353
'/about',
5454
];
5555

56-
isNightMode = false;
56+
isNightMode = computed(() => this.themeService.theme() === 'dark');
5757

5858
constructor(
5959
private themeService: ThemeService,
@@ -66,14 +66,7 @@ export class SidenavButtonsComponent implements OnInit {
6666
);
6767
}
6868

69-
ngOnInit(): void {
70-
const currentTheme = this.themeService.getTheme();
71-
this.isNightMode = currentTheme === 'dark';
72-
}
73-
7469
toggleTheme(): void {
75-
this.isNightMode = !this.isNightMode;
76-
const newTheme = this.isNightMode ? 'dark' : 'light';
77-
this.themeService.setTheme(newTheme);
70+
this.themeService.toggleTheme();
7871
}
7972
}

src/app/service/theme.service.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Injectable } from '@angular/core';
2-
import { BehaviorSubject } from 'rxjs';
1+
import { Injectable, signal } from '@angular/core';
2+
import { toObservable } from '@angular/core/rxjs-interop';
33

44
export type AppTheme = 'light' | 'dark';
55

@@ -8,8 +8,8 @@ export class ThemeService {
88
private readonly STORAGE_KEY = 'theme';
99
private readonly defaultTheme: AppTheme = 'light';
1010

11-
private themeSubject = new BehaviorSubject<AppTheme>(this.defaultTheme);
12-
public readonly theme$ = this.themeSubject.asObservable();
11+
readonly theme = signal<AppTheme>(this.defaultTheme);
12+
readonly theme$ = toObservable(this.theme);
1313

1414
constructor() {}
1515

@@ -20,7 +20,7 @@ export class ThemeService {
2020
}
2121

2222
setTheme(theme: AppTheme): void {
23-
if (this.themeSubject.value === theme) return;
23+
if (this.theme() === theme) return;
2424
this.applyTheme(theme);
2525
}
2626

@@ -29,14 +29,14 @@ export class ThemeService {
2929
document.body.classList.add(`${theme}-theme`);
3030

3131
localStorage.setItem(this.STORAGE_KEY, theme);
32-
this.themeSubject.next(theme);
32+
this.theme.set(theme);
3333
}
3434

3535
getTheme(): AppTheme {
36-
return this.themeSubject.value;
36+
return this.theme();
3737
}
3838

3939
toggleTheme(): void {
40-
this.setTheme(this.themeSubject.value === 'light' ? 'dark' : 'light');
40+
this.setTheme(this.theme() === 'light' ? 'dark' : 'light');
4141
}
4242
}

src/app/service/title.service.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Injectable } from '@angular/core';
2-
import { BehaviorSubject } from 'rxjs';
1+
import { Injectable, signal } from '@angular/core';
2+
import { toObservable } from '@angular/core/rxjs-interop';
33

44
export interface TitleInfo {
55
level?: number;
@@ -9,17 +9,17 @@ export interface TitleInfo {
99

1010
@Injectable({ providedIn: 'root' })
1111
export class TitleService {
12-
private titleSubject = new BehaviorSubject<TitleInfo | null>(null);
13-
public readonly titleInfo$ = this.titleSubject.asObservable();
12+
readonly titleInfo = signal<TitleInfo | null>(null);
13+
readonly titleInfo$ = toObservable(this.titleInfo);
1414

1515
constructor() {}
1616

1717
setTitle(titleInfo: TitleInfo | null): void {
18-
this.titleSubject.next(titleInfo);
18+
this.titleInfo.set(titleInfo);
1919
}
2020

2121
clearTitle(): void {
22-
this.titleSubject.next(null);
22+
this.titleInfo.set(null);
2323
}
2424

2525
formatTitle(titleInfo: TitleInfo | null, defaultTitle: string): string {

0 commit comments

Comments
 (0)