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

Ionic+Angular中prefers-color-scheme事件仅触发一次问题求助

问题

我正在使用Ionic和Angular开发项目,尝试根据用户设备的主题偏好自动切换明暗模式,但发现window.matchMedia('(prefers-color-scheme: dark)')对应的事件仅触发一次。

环境信息

Angular CLI: 14.2.3
Node: 16.17.0
Package Manager: npm 8.19.1
OS: win32 x64

Angular: 14.2.3
... cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                         Version
---------------------------------------------------------       
@angular-devkit/architect       0.1402.3
@angular-devkit/build-angular   14.2.3
@angular-devkit/core            14.2.3
@angular-devkit/schematics      14.2.3
@schematics/angular             14.2.3
rxjs                            6.6.7
typescript                      4.7.4

Ionic version: 6.20.1

现有代码

service.ts

setListenerTheme(): void {
    window.matchMedia('(prefers-color-scheme: dark)').onchange = (e) => {
      console.log("ESCUCHANDO CAMBIOS EN THEME")
      const buttonToggleTheme = document.querySelector('#themeToggle');

      buttonToggleTheme.toggleAttribute('checked');
    };
  }

  toggleTheme(event: any): void {
    this.dark_mode = event.detail.checked;
    document.body.classList.toggle('dark', event.detail.checked);
  }

app.component.ts

export class AppComponent implements OnInit {

  constructor(
    private userService: UserService,
  ) {}

  ngOnInit(): void {
    this.userService.setListenerTheme();
  }

}
解决方案

问题根源在于直接使用onchange赋值监听、未保留媒体查询实例引用,且缺少初始主题状态检查,导致监听失效或仅触发一次。以下是修复方案:

修改后的service.ts代码

import { Injectable, OnDestroy } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class UserService implements OnDestroy {
  dark_mode: boolean = false;
  private mediaQueryList: MediaQueryList | undefined;

  setListenerTheme(): void {
    // 保存媒体查询实例引用,避免重复创建
    this.mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
    
    // 初始加载时主动检查设备主题,设置初始状态
    this.updateTheme(this.mediaQueryList.matches);
    
    // 使用addEventListener绑定事件,比onchange更可靠
    this.mediaQueryList.addEventListener('change', (e) => {
      console.log("检测到主题变化");
      this.updateTheme(e.matches);
    });
  }

  // 统一处理主题更新逻辑
  private updateTheme(isDark: boolean): void {
    const buttonToggleTheme = document.querySelector('#themeToggle');
    if (buttonToggleTheme) {
      // 根据设备主题状态同步开关,避免无效切换
      const currentChecked = buttonToggleTheme.hasAttribute('checked');
      if (isDark !== currentChecked) {
        buttonToggleTheme.toggleAttribute('checked');
      }
    }
    // 同步更新service状态和body类名
    this.dark_mode = isDark;
    document.body.classList.toggle('dark', isDark);
  }

  toggleTheme(event: any): void {
    this.dark_mode = event.detail.checked;
    document.body.classList.toggle('dark', event.detail.checked);
  }

  // 组件销毁时移除监听,防止内存泄漏
  ngOnDestroy(): void {
    if (this.mediaQueryList) {
      this.mediaQueryList.removeEventListener('change', () => {});
    }
  }
}

修改后的app.component.ts代码

import { Component, OnInit, OnDestroy } from '@angular/core';
import { UserService } from './services/user.service';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
})
export class AppComponent implements OnInit, OnDestroy {

  constructor(
    private userService: UserService,
  ) {}

  ngOnInit(): void {
    this.userService.setListenerTheme();
  }

  // 销毁组件时调用service的销毁方法
  ngOnDestroy(): void {
    this.userService.ngOnDestroy();
  }

}

关键修复点说明

  • 保留MediaQueryList实例:避免每次调用window.matchMedia创建新对象,确保监听的是同一个媒体查询实例。
  • 使用addEventListener:替代onchange赋值,防止监听被意外覆盖,同时支持绑定多个事件处理函数。
  • 初始主题检查:页面加载时主动获取设备主题状态,确保初始显示与用户偏好一致。
  • 统一主题更新逻辑:将主题切换的操作封装到updateTheme方法中,避免代码重复,保证状态同步。
  • 移除监听:在组件销毁时清理事件监听,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 19:45:40