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
相关产品推荐
相关产品推荐

