Angular Material:如何实现预构建主题的动态切换菜单
我之前做过类似的功能,跟你说下具体步骤,完全用官方预构建主题就能搞定:
1. 引入所有预构建主题文件
首先,你需要把四个预构建主题的CSS都添加到项目配置里,让Angular识别这些文件。打开angular.json,找到projects -> 你的项目名 -> architect -> build -> options -> styles数组,把四个主题路径加进去:
"styles": [ "src/styles.scss", "@angular/material/prebuilt-themes/deeppurple-amber.css", "@angular/material/prebuilt-themes/indigo-pink.css", "@angular/material/prebuilt-themes/pink-bluegrey.css", "@angular/material/prebuilt-themes/purple-green.css" ],
这里要注意:默认Angular会把这些CSS打包进项目,但我们只需要同时激活一个主题,后续会通过逻辑控制哪个主题生效。
2. 创建主题切换服务(可选但推荐)
为了让切换逻辑更清晰,也方便在各个组件里调用,最好做一个专门的服务。用Angular CLI生成:
ng generate service theme
在theme.service.ts里实现主题保存、切换、状态通知的功能:
import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class ThemeService { // 定义四个预构建主题的标识名 availableThemes = [ 'deeppurple-amber', 'indigo-pink', 'pink-bluegrey', 'purple-green' ]; // 用BehaviorSubject保存当前主题,默认使用官网的indigo-pink private currentThemeSubject = new BehaviorSubject<string>('indigo-pink'); currentTheme$ = this.currentThemeSubject.asObservable(); constructor() { // 从localStorage读取之前保存的主题,没有则用默认值 const savedTheme = localStorage.getItem('selected-theme'); if (savedTheme && this.availableThemes.includes(savedTheme)) { this.currentThemeSubject.next(savedTheme); this.applyTheme(savedTheme); } } switchTheme(themeName: string) { if (this.availableThemes.includes(themeName)) { this.currentThemeSubject.next(themeName); this.applyTheme(themeName); localStorage.setItem('selected-theme', themeName); } } private applyTheme(themeName: string) { // 获取所有主题对应的link标签 const themeLinks = Array.from(document.querySelectorAll('link[rel="stylesheet"]')) .filter(link => link.href.includes('prebuilt-themes')); // 禁用所有主题,只激活选中的那个 themeLinks.forEach(link => { link.disabled = !link.href.includes(themeName); }); } }
核心思路是:页面加载时所有主题CSS都会被加载,但默认只有一个启用;切换时禁用其他主题的link标签,启用选中的主题。
3. 创建主题切换菜单组件
接下来做一个用户可交互的选择菜单,比如用Angular Material的mat-select组件,先生成组件:
ng generate component theme-selector
在theme-selector.component.html里写菜单结构:
<mat-form-field appearance="fill"> <mat-label>选择主题</mat-label> <mat-select [(value)]="selectedTheme" (selectionChange)="onThemeChange($event.value)"> <mat-option *ngFor="let theme of themeService.availableThemes" [value]="theme"> {{ theme.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ') }} </mat-option> </mat-select> </mat-form-field>
然后在theme-selector.component.ts里关联服务:
import { Component, OnInit } from '@angular/core'; import { ThemeService } from '../theme.service'; @Component({ selector: 'app-theme-selector', templateUrl: './theme-selector.component.html', styleUrls: ['./theme-selector.component.scss'] }) export class ThemeSelectorComponent implements OnInit { selectedTheme!: string; constructor(public themeService: ThemeService) {} ngOnInit(): void { // 订阅主题变化,同步菜单选中状态 this.themeService.currentTheme$.subscribe(theme => { this.selectedTheme = theme; }); } onThemeChange(themeName: string) { this.themeService.switchTheme(themeName); } }
4. 在页面中挂载主题选择器
最后把这个选择器放到导航栏或者页面顶部,比如在app.component.html里:
<mat-toolbar color="primary"> <span>我的Angular项目</span> <span class="spacer"></span> <app-theme-selector></app-theme-selector> </mat-toolbar> <router-outlet></router-outlet>
记得在app.module.ts里导入需要的Material模块,比如MatSelectModule、MatFormFieldModule、MatToolbarModule,不然组件会报错。
额外优化小技巧
- 可以给每个主题选项加个预览色块,比如在
mat-option里加个小div,用对应主题的主色填充背景,让用户更直观感知主题风格。 - 如果在意初始加载速度,也可以改成动态加载:当用户选择某个主题时,再创建link标签加载对应的CSS文件,不过四个预构建主题文件都很小,差异不大。
这样实现后,效果就和Angular Material官网的主题切换菜单差不多了,用户选择不同主题时,页面所有Material组件的样式会立刻切换。
内容的提问来源于stack exchange,提问作者Maithilish

