Angular 5中使用ChangeDetectionStrategy.OnPush时如何从服务推送变更至组件
解决Angular OnPush策略下服务向组件推送变更的问题
首先得搞清楚切换到ChangeDetectionStrategy.OnPush后组件没更新的原因:OnPush模式下,Angular只会在输入属性引用变化、组件内部触发事件、手动触发变更检测,或者订阅的Observable发出新值时才会执行变更检测。你之前的服务是直接替换items数组的引用,但组件没收到“变化通知”,所以视图自然不会更新。
下面给你两种最实用的解决方案:
方案1:用Observable(推荐,贴合Angular响应式风格)
把服务里的items改成可观察对象(比如BehaviorSubject),组件通过async管道订阅它——async管道会自动处理订阅逻辑,并且在Observable发出新值时自动触发变更检测,完美适配OnPush策略。
修改后的服务代码:
export class Some{ public name: string; } @Injectable({ providedIn: 'root' }) // 建议用providedIn:root来全局提供服务 export class Service { // 用BehaviorSubject保存数据,初始值设为空数组 private itemsSubject = new BehaviorSubject<Some[]>([]); // 对外暴露只读的Observable,避免组件直接修改数据源 public items$ = this.itemsSubject.asObservable(); constructor(private http: HttpClient) { this.getModel(); } getModel(){ this.http.get<Some[]>("some_url") .subscribe((result: Some[])=>{ // 通过next方法推送新数据 this.itemsSubject.next(result); }); } }
组件模板里用async管道订阅数据:
<!-- 组件模板示例 --> <ul> <li *ngFor="let item of service.items$ | async">{{item.name}}</li> </ul>
组件类只需注入服务即可:
@Component({ selector: 'app-some-component', templateUrl: './some-component.component.html', changeDetection: ChangeDetectionStrategy.OnPush // 开启OnPush策略 }) export class SomeComponent { constructor(public service: Service) {} }
方案2:手动触发变更检测
如果不想用Observable,你可以在组件里注入ChangeDetectorRef,当服务的数据更新后,手动通知Angular执行变更检测。
先修改服务,添加一个事件通知组件数据更新:
export class Some{ public name: string; } @Injectable({ providedIn: 'root' }) export class Service { public items: Some[] = []; // 用EventEmitter发出数据更新通知 public itemsUpdated = new EventEmitter<Some[]>(); constructor(private http: HttpClient) { this.getModel(); } getModel(){ this.http.get<Some[]>("some_url") .subscribe((result: Some[])=>{ this.items = result; // 推送更新通知 this.itemsUpdated.emit(result); }); } }
然后在组件里订阅这个事件,手动触发变更检测:
import { ChangeDetectorRef, Component, OnDestroy } from '@angular/core'; import { Service } from './service'; import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; @Component({ selector: 'app-some-component', templateUrl: './some-component.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class SomeComponent implements OnDestroy { private destroy$ = new Subject<void>(); constructor(public service: Service, private cdr: ChangeDetectorRef) { // 订阅服务的更新事件,组件销毁时自动取消订阅 this.service.itemsUpdated.pipe(takeUntil(this.destroy$)) .subscribe(() => { // 标记组件需要进行变更检测 this.cdr.markForCheck(); // 或者直接执行变更检测:this.cdr.detectChanges(); }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } }
模板直接绑定服务的items即可:
<ul> <li *ngFor="let item of service.items">{{item.name}}</li> </ul>
小提醒
- 优先选方案1,它更符合Angular的响应式设计理念,还能减少手动管理变更检测的麻烦。
- 用方案2时,一定要记得在组件销毁时取消订阅事件,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Andrew G
相关产品推荐
相关产品推荐

