Angular Service无法触发即时变更问题求助
屏幕宽度监听迁移至Service后布局无法自动更新的解决方案
临近毕业制作个人作品集网站,选用Angular框架开发。原本在header组件内实现的屏幕宽度监听、根据布尔值切换布局的逻辑,因多页面需要迁移至全局Service后,出现移动端与全屏模式切换时布局无法自动更新,需手动刷新的问题,此前组件内实现无此问题,尝试过Observers、ChangeDetectorRef均无效,求可行方案。
现有代码
header.component.html
<!-- Navigation for mobile --> <header *ngIf="screenwidthService.isMobile"> <div class="nav-icon-independent"> <img class="hover" *ngIf="status" src="/assets/img/navOpened.svg" (click)="toggleNavigation()"> <img class="hover" *ngIf="!status" src="/assets/img/navClosed.svg" (click)="toggleNavigation()"> </div> <div class="nav-sidebar" [class.toggleSidebar]="status"> <nav> <ul class="nav-ul"> <li *ngFor="let link of links" class="nav-li hover">{{ link }}</li> </ul> </nav> </div> </header> <!-- Navigation for Tablet && Screen --> <header *ngIf="screenwidthService.isScreen"> <nav> <ul class="nav-ul"> <li *ngFor="let link of links" class="nav-li hover">{{ link }}</li> </ul> </nav> </header>
header.component.ts
import { Component } from '@angular/core'; import { ScreenwidthService } from 'src/app/services/screenwidth.service'; @Component({ selector: 'app-header', templateUrl: './header.component.html', styleUrls: ['./header.component.css'] }) export class HeaderComponent { constructor( public screenwidthService: ScreenwidthService, ) {} // State of the navigation sidebar status: Boolean = false; // Link tags for navigation links = ['Home', 'About me', 'Projects']; // Toggles the navigation sidebar in mobile toggleNavigation() { this.status = !this.status } }
screenwidth.service.ts
/* * Service to listen to the screenwidth */ import { Injectable, HostListener } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class ScreenwidthService { isMobile: boolean = true; isScreen: boolean = false; constructor() { this.checkScreenSize(); } @HostListener('window:resize', ['$event']) onResize(event: any): void { this.checkScreenSize(); } private checkScreenSize(): void { this.isMobile = window.innerWidth < 1079; this.isScreen = window.innerWidth >= 1079; } }
问题原因
Service中的isMobile和isScreen是普通布尔变量,Angular的变更检测系统不会自动追踪这些变量的变化——组件无法感知到Service内部变量的更新,因此视图不会自动刷新,必须手动刷新页面才能看到变化。
解决方案:使用RxJS Observable实现状态通知
通过RxJS的BehaviorSubject来管理屏幕状态,让组件可以订阅状态变化,Angular会自动触发变更检测更新视图。
修改后的ScreenwidthService
/* * Service to listen to the screenwidth */ import { Injectable, HostListener } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class ScreenwidthService { // 使用BehaviorSubject保存当前状态,并允许组件订阅变化 private isMobileSubject = new BehaviorSubject<boolean>(true); private isScreenSubject = new BehaviorSubject<boolean>(false); // 对外暴露可观察对象,避免组件直接修改内部状态 isMobile$ = this.isMobileSubject.asObservable(); isScreen$ = this.isScreenSubject.asObservable(); constructor() { this.checkScreenSize(); } @HostListener('window:resize', ['$event']) onResize(event: any): void { this.checkScreenSize(); } private checkScreenSize(): void { const isMobile = window.innerWidth < 1079; const isScreen = window.innerWidth >= 1079; // 发射新状态,通知所有订阅组件 this.isMobileSubject.next(isMobile); this.isScreenSubject.next(isScreen); } }
修改后的HeaderComponent
import { Component, OnInit } from '@angular/core'; import { ScreenwidthService } from 'src/app/services/screenwidth.service'; import { Observable } from 'rxjs'; @Component({ selector: 'app-header', templateUrl: './header.component.html', styleUrls: ['./header.component.css'] }) export class HeaderComponent implements OnInit { // 声明可观察对象,用于模板订阅 isMobile$!: Observable<boolean>; isScreen$!: Observable<boolean>; constructor( private screenwidthService: ScreenwidthService, ) {} ngOnInit(): void { // 订阅Service中的状态变化 this.isMobile$ = this.screenwidthService.isMobile$; this.isScreen$ = this.screenwidthService.isScreen$; } // State of the navigation sidebar status: boolean = false; // Link tags for navigation links = ['Home', 'About me', 'Projects']; // Toggles the navigation sidebar in mobile toggleNavigation() { this.status = !this.status; } }
修改后的header.component.html
使用Angular的async管道自动订阅Observable,当状态变化时自动更新视图:
<!-- Navigation for mobile --> <header *ngIf="isMobile$ | async"> <div class="nav-icon-independent"> <img class="hover" *ngIf="status" src="/assets/img/navOpened.svg" (click)="toggleNavigation()"> <img class="hover" *ngIf="!status" src="/assets/img/navClosed.svg" (click)="toggleNavigation()"> </div> <div class="nav-sidebar" [class.toggleSidebar]="status"> <nav> <ul class="nav-ul"> <li *ngFor="let link of links" class="nav-li hover">{{ link }}</li> </ul> </nav> </div> </header> <!-- Navigation for Tablet && Screen --> <header *ngIf="isScreen$ | async"> <nav> <ul class="nav-ul"> <li *ngFor="let link of links" class="nav-li hover">{{ link }}</li> </ul> </nav> </header>
效果说明
BehaviorSubject会保存当前的屏幕状态,组件初始化时就能获取到正确的初始值- 当窗口大小变化时,Service发射新的状态值,所有订阅的组件会自动收到通知
async管道会自动处理订阅和取消订阅,避免内存泄漏,同时触发Angular的变更检测,自动更新视图
内容的提问来源于stack exchange,提问作者Stef Verniers
相关产品推荐
相关产品推荐

