You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 16+Angular Material组件级明暗主题切换实现求助

Angular明暗主题切换实现方案

现有代码问题说明

  • 当前styles.scss依赖系统偏好自动切换主题,无法响应手动切换操作
  • app.component未正确接收并处理header组件发射的主题切换事件
  • 主题状态未持久化,刷新页面会重置

1. 调整styles.scss:改用类控制主题切换

将原有的媒体查询自动切换逻辑,改为基于body类名的主题应用,这样可以通过添加/移除类来手动控制主题:

@use '@angular/material' as mat;

@include mat.core();

// 定义深色主题
$dark-theme: mat.define-dark-theme((
  color: (
    primary: mat.define-palette(mat.$pink-palette),
    accent: mat.define-palette(mat.$blue-grey-palette),
  ),
  typography: mat.define-typography-config(),
  density: 0,
));

// 定义浅色主题
$light-theme: mat.define-light-theme((
  color: (
    primary: mat.define-palette(mat.$indigo-palette),
    accent: mat.define-palette(mat.$pink-palette),
  ),
));

// 全局基础样式
* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  // 添加主题切换过渡动画
  transition: background-color 0.3s ease, color 0.3s ease;
}

body {
  margin: 0;
  padding: 0;

  app-root {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
  }
}

// 浅色主题样式集合
body.light-theme {
  @include mat.core-theme($light-theme);
  @include mat.button-theme($light-theme);
  @include mat.toolbar-theme($light-theme);
  @include mat.slide-toggle-theme($light-theme);
  // 可根据需求添加其他Material组件主题
}

// 深色主题样式集合
body.dark-theme {
  @include mat.core-theme($dark-theme);
  @include mat.button-theme($dark-theme);
  @include mat.toolbar-theme($dark-theme);
  @include mat.slide-toggle-theme($dark-theme);
}

// 默认主题:优先匹配系统偏好
@media (prefers-color-scheme: light) {
  body {
    @extend .light-theme;
  }
}
@media (prefers-color-scheme: dark), (prefers-color-scheme: no-preference) {
  body {
    @extend .dark-theme;
  }
}

2. 修正app.component.ts:处理主题切换与持久化

接收header的主题切换事件,更新DOM类名,并将用户选择保存到本地存储:

import {Component, OnInit} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  title = 'web';
  activeTheme: boolean; // true=浅色主题,false=深色主题

  ngOnInit(): void {
    // 初始化主题:优先读取本地存储的用户选择,否则匹配系统偏好
    const savedTheme = localStorage.getItem('activeTheme');
    if (savedTheme) {
      this.activeTheme = savedTheme === 'true';
      this.applyTheme(this.activeTheme);
    } else {
      const isLightPreferred = window.matchMedia('(prefers-color-scheme: light)').matches;
      this.activeTheme = isLightPreferred;
      this.applyTheme(this.activeTheme);
    }
  }

  onThemeChange(themeValue: boolean): void {
    this.activeTheme = themeValue;
    this.applyTheme(themeValue);
    localStorage.setItem('activeTheme', themeValue.toString());
  }

  private applyTheme(isLight: boolean): void {
    const body = document.body;
    if (isLight) {
      body.classList.remove('dark-theme');
      body.classList.add('light-theme');
    } else {
      body.classList.remove('light-theme');
      body.classList.add('dark-theme');
    }
  }
}

3. 修正app.component.html:正确绑定事件

将原有的事件绑定改为接收事件参数的形式:

<header id="header">
  <app-header (changeTheme)="onThemeChange($event)" [checked]="activeTheme"></app-header>
</header>

<main id="main">
  <router-outlet></router-outlet>
</main>

<footer id="footer">
  <app-footer></app-footer>
</footer>

4. 同步header组件状态

让开关状态与当前主题保持一致,修改header.component.ts添加输入属性:

import {AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild} from '@angular/core';

@Component({
  selector: 'app-header',
  styleUrls: ['./header.component.scss'],
  templateUrl: 'header.component.html'
})
export class HeaderComponent implements AfterViewInit {
  sun = 'light-icon'; // 替换为你的SVG路径
  moon = 'dark-icon'; // 替换为你的SVG路径

  brand: string = 'MyApp';
  @Input() checked: boolean = false; // 接收父组件的主题状态
  @Output() changeTheme = new EventEmitter<boolean>();

  @ViewChild('darkModeSwitch', {read: ElementRef}) element: ElementRef | undefined

  ngAfterViewInit(): void {
    if (this.element) {
      this.element.nativeElement.querySelector('.mdc-switch__icon--on').firstChild.setAttribute('d', this.moon);
      this.element.nativeElement.querySelector('.mdc-switch__icon--off').firstChild.setAttribute('d', this.sun);
    }
  }

  switchTheme() {
    this.checked = !this.checked;
    this.changeTheme.emit(this.checked);
  }
}

内容的提问来源于stack exchange,提问作者Fadly Dzil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 20:38:18