Theming Service
Il ThemingService fornisce gestione dinamica dei temi dell'applicazione, permettendo agli utenti di cambiare tema a runtime con persistenza delle preferenze.
Panoramica
Il ThemingService gestisce:
- ✅ Switch dinamico: Cambio tema a runtime senza reload
- ✅ Persistenza: Salvataggio preferenze in localStorage
- ✅ Observable: Reactive updates con RxJS
- ✅ Type-safe: Supporto TypeScript completo
- ✅ Multi-UI Kit: Supporto per PrimeNG, Bootstrap, Material
- ✅ Custom themes: Supporto per temi personalizzati
Installazione
Il servizio è configurato automaticamente con provideTessUiLibrary():
import { provideTessUiLibrary } from '@tess-ui-library/core';
export const appConfig: ApplicationConfig = {
providers: [
provideTessUiLibrary({
theme: {
defaultTheme: 'light',
themes: ['light', 'dark', 'custom'],
},
}),
],
};
Uso Base
Cambiare Tema
import { Component, inject } from '@angular/core';
import { ThemingService } from '@tess-ui-library/core';
@Component({
selector: 'app-theme-switcher',
standalone: true,
template: `
<button (click)="toggleTheme()">
{{ currentTheme === 'light' ? '🌙' : '☀️' }} Toggle Theme
</button>
`,
})
export class ThemeSwitcherComponent {
private themingService = inject(ThemingService);
currentTheme = this.themingService.getCurrentTheme();
toggleTheme() {
const newTheme = this.currentTheme === 'light' ? 'dark' : 'light';
this.themingService.setTheme(newTheme);
this.currentTheme = newTheme;
}
}
Observable per Cambiamenti
import { Component, inject, OnInit } from '@angular/core';
import { ThemingService } from '@tess-ui-library/core';
@Component({
selector: 'app-layout',
standalone: true,
template: `
<div [attr.data-theme]="currentTheme">
<router-outlet></router-outlet>
</div>
`,
})
export class LayoutComponent implements OnInit {
private themingService = inject(ThemingService);
currentTheme = '';
ngOnInit() {
// Subscribe ai cambiamenti di tema
this.themingService.currentTheme$.subscribe(theme => {
this.currentTheme = theme;
document.body.setAttribute('data-theme', theme);
});
}
}
API
setTheme(themeName: string): void
Imposta il tema corrente.
this.themingService.setTheme('dark');
getCurrentTheme(): string
Ritorna il nome del tema corrente.
const theme = this.themingService.getCurrentTheme();
console.log('Tema corrente:', theme);
currentTheme$: Observable<string>
Observable che emette il tema ogni volta che cambia.
this.themingService.currentTheme$.subscribe(theme => {
console.log('Nuovo tema:', theme);
});
getAvailableThemes(): string[]
Ritorna la lista dei temi disponibili.
const themes = this.themingService.getAvailableThemes();
console.log('Temi disponibili:', themes);
registerTheme(theme: ThemeDefinition): void
Registra un tema personalizzato.
this.themingService.registerTheme({
name: 'ocean',
displayName: 'Ocean Blue',
cssPath: '/assets/themes/ocean.css',
isDark: false,
});
Configurazione Temi
PrimeNG Themes
Per PrimeNG, configura i temi in angular.json:
{
"projects": {
"your-app": {
"architect": {
"build": {
"options": {
"styles": [
{
"input": "node_modules/@primeuix/themes/aura/aura-light-blue/theme.css",
"bundleName": "light",
"inject": false
},
{
"input": "node_modules/@primeuix/themes/aura/aura-dark-blue/theme.css",
"bundleName": "dark",
"inject": false
}
]
}
}
}
}
}
}
Poi nel servizio:
// app.config.ts
import { THEME_CONFIG } from '@tess-ui-library/core';
export const appConfig: ApplicationConfig = {
providers: [
{
provide: THEME_CONFIG,
useValue: {
themes: [
{
name: 'light',
displayName: 'Light Theme',
cssPath: 'light.css',
isDark: false,
},
{
name: 'dark',
displayName: 'Dark Theme',
cssPath: 'dark.css',
isDark: true,
},
],
defaultTheme: 'light',
},
},
],
};
Esempi Pratici
Theme Switcher Dropdown
import { Component, inject, OnInit } from '@angular/core';
import { ThemingService } from '@tess-ui-library/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-theme-selector',
standalone: true,
imports: [CommonModule],
template: `
<div class="theme-selector">
<label>Tema:</label>
<select [(ngModel)]="selectedTheme" (change)="onThemeChange()">
@for (theme of availableThemes; track theme) {
<option [value]="theme">{{ theme | titlecase }}</option>
}
</select>
</div>
`,
})
export class ThemeSelectorComponent implements OnInit {
private themingService = inject(ThemingService);
availableThemes: string[] = [];
selectedTheme = '';
ngOnInit() {
this.availableThemes = this.themingService.getAvailableThemes();
this.selectedTheme = this.themingService.getCurrentTheme();
}
onThemeChange() {
this.themingService.setTheme(this.selectedTheme);
}
}
Auto Dark Mode (Sistema)
@Injectable({ providedIn: 'root' })
export class AutoThemeService {
private themingService = inject(ThemingService);
initialize() {
// Controlla preferenza sistema
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');
// Imposta tema iniziale
const theme = darkModeQuery.matches ? 'dark' : 'light';
this.themingService.setTheme(theme);
// Listen a cambiamenti preferenza sistema
darkModeQuery.addEventListener('change', (e) => {
const newTheme = e.matches ? 'dark' : 'light';
this.themingService.setTheme(newTheme);
});
}
}
// In app.config.ts
export function initializeTheme(): () => void {
return () => {
const autoTheme = inject(AutoThemeService);
autoTheme.initialize();
};
}
export const appConfig: ApplicationConfig = {
providers: [
{
provide: APP_INITIALIZER,
useFactory: initializeTheme,
multi: true,
},
],
};
Theme con Preferenze Utente
@Injectable({ providedIn: 'root' })
export class UserPreferencesService {
private themingService = inject(ThemingService);
private localStorage = inject(TessLocalStorageService);
private http = inject(HttpClient);
loadUserPreferences(userId: string) {
this.http.get<UserPreferences>(\`/api/users/\${userId}/preferences\`)
.subscribe(prefs => {
if (prefs.theme) {
this.themingService.setTheme(prefs.theme);
}
});
}
saveThemePreference(theme: string) {
// Salva localmente
this.localStorage.set('user-theme', theme);
// Salva sul server
this.http.put('/api/users/me/preferences', { theme })
.subscribe();
}
constructor() {
// Listen ai cambiamenti di tema e salva
this.themingService.currentTheme$.subscribe(theme => {
this.saveThemePreference(theme);
});
}
}
Custom Theme con CSS Variables
/* assets/themes/ocean.css */
:root[data-theme="ocean"] {
--primary-color: #0077BE;
--secondary-color: #00A8CC;
--surface-color: #F0F8FF;
--text-color: #333333;
--border-color: #D0E8F2;
}
:root[data-theme="ocean"] .p-button {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
// Registra tema custom
this.themingService.registerTheme({
name: 'ocean',
displayName: 'Ocean Theme',
cssPath: '/assets/themes/ocean.css',
isDark: false,
variables: {
primaryColor: '#0077BE',
secondaryColor: '#00A8CC',
surfaceColor: '#F0F8FF',
},
});
Theme Switcher con Animazione
@Component({
selector: 'app-theme-switcher',
standalone: true,
template: `
<button
class="theme-toggle"
(click)="toggleTheme()"
[class.switching]="isSwitching"
>
<i [class]="currentThemeIcon"></i>
</button>
`,
styles: [`
.theme-toggle {
transition: transform 0.3s ease;
}
.theme-toggle.switching {
transform: rotate(180deg);
}
`],
})
export class ThemeSwitcherComponent {
private themingService = inject(ThemingService);
currentTheme = this.themingService.getCurrentTheme();
isSwitching = false;
get currentThemeIcon() {
return this.currentTheme === 'dark' ? 'pi pi-sun' : 'pi pi-moon';
}
toggleTheme() {
this.isSwitching = true;
const newTheme = this.currentTheme === 'light' ? 'dark' : 'light';
// Ritarda il cambio per animazione
setTimeout(() => {
this.themingService.setTheme(newTheme);
this.currentTheme = newTheme;
setTimeout(() => {
this.isSwitching = false;
}, 300);
}, 150);
}
}
Multi-Tenant Theming
@Injectable({ providedIn: 'root' })
export class TenantThemeService {
private themingService = inject(ThemingService);
private http = inject(HttpClient);
loadTenantTheme(tenantId: string) {
this.http.get<TenantConfig>(\`/api/tenants/\${tenantId}/config\`)
.subscribe(config => {
if (config.customTheme) {
// Registra tema custom del tenant
this.themingService.registerTheme({
name: \`tenant-\${tenantId}\`,
displayName: config.themeName,
cssPath: config.themeCssUrl,
isDark: config.isDarkTheme,
});
// Applica tema
this.themingService.setTheme(\`tenant-\${tenantId}\`);
}
});
}
}
Integrazione con UI Kit
PrimeNG
import { PrimeNGConfig } from 'primeng/api';
@Component({
selector: 'app-root',
standalone: true,
template: `...`,
})
export class AppComponent implements OnInit {
private primengConfig = inject(PrimeNGConfig);
private themingService = inject(ThemingService);
ngOnInit() {
this.themingService.currentTheme$.subscribe(theme => {
// Aggiorna configurazione PrimeNG
this.primengConfig.ripple = true;
// Carica CSS del tema
this.loadThemeCSS(theme);
});
}
private loadThemeCSS(theme: string) {
const linkId = 'theme-css';
let link = document.getElementById(linkId) as HTMLLinkElement;
if (!link) {
link = document.createElement('link');
link.id = linkId;
link.rel = 'stylesheet';
document.head.appendChild(link);
}
link.href = \`\${theme}.css\`;
}
}
Best Practices
- Persistenza: Salva sempre la preferenza del tema in localStorage
- Default sensato: Usa il tema sistema come default se disponibile
- Transizioni smooth: Aggiungi transizioni CSS per cambio tema graduale
- Accessibilità: Assicurati che i temi mantengano contrasti adeguati (WCAG AA)
- Performance: Lazy load CSS dei temi non usati
- Testing: Testa l'app con tutti i temi disponibili
- Custom properties: Usa CSS variables per facilità di personalizzazione
- Documentazione: Documenta i colori e variabili di ogni tema
CSS Variables
Definisci variabili CSS per ogni tema:
/* light-theme.css */
:root {
--bg-primary: #ffffff;
--bg-secondary: #f8f9fa;
--text-primary: #212529;
--text-secondary: #6c757d;
--border-color: #dee2e6;
}
/* dark-theme.css */
:root {
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--text-primary: #ffffff;
--text-secondary: #b0b0b0;
--border-color: #404040;
}
Usa le variabili nei componenti:
.component {
background-color: var(--bg-primary);
color: var(--text-primary);
border-color: var(--border-color);
}