Tauri+Angular项目:如何从后端强制更新视图?求最优解
我正在开发一个前端基于Angular的Tauri项目,目前遇到无法从后端强制更新Angular视图的问题。我编写了一个向组件提供数据的服务,该服务会监听后端的「data-updated」信号以更新数据。
服务代码如下:
export class MyService { public data: string[] = []; public serviceUpdated = new EventEmitter<void>(); public constructor() { this.updateService(); listen('data-updated', () => { this.updateService(); }) } private updateService() { forkJoin({ // ask backend to send new data }).subscribe(({newData}) => { data = newData; this.serviceUpdated.emit(); }) } }
当服务接收到后端的「data-updated」信号时,会请求后端发送最新数据并保存到自身。组件代码如下:
export class MyComponent { data: string[] = []; constructor(private myService: MyService) { myService.serviceUpdated.subscribe((_) => { this.updateData(); }); } public updateData() { this.data = this.myService.data; } }
服务更新数据后会触发serviceUpdated事件,订阅该事件的组件会调用updateData()方法,但新数据不会立即显示在前端,除非用户进行点击按钮等界面操作。我曾尝试在组件构造函数中每50毫秒调用一次ChangeDetectorRef的detectChanges()方法,但这是临时的糟糕方案。请问有没有无需调用detectChanges()的更好解决方案?
1. 修复服务中的变量赋值错误
服务代码里的data = newData未使用this,导致赋值的是局部变量而非服务实例的data属性,组件根本拿不到更新后的数据。修正代码:
private updateService() { forkJoin({ // ask backend to send new data }).subscribe(({newData}) => { this.data = newData; // 加上this,赋值到实例属性 this.serviceUpdated.emit(); }) }
2. 将Tauri事件回调纳入Angular变更检测域
Tauri的listen方法运行在Angular的NgZone之外,Angular无法自动检测到回调内的状态变化。需要在服务中注入NgZone,把回调逻辑包裹在zone.run()里:
import { NgZone } from '@angular/core'; import { listen } from '@tauri-apps/api/event'; export class MyService { public data: string[] = []; public serviceUpdated = new EventEmitter<void>(); public constructor(private zone: NgZone) { // 注入NgZone this.updateService(); listen('data-updated', () => { this.zone.run(() => { // 把回调逻辑放在NgZone内执行 this.updateService(); }); }) } private updateService() { forkJoin({ // ask backend to send new data }).subscribe(({newData}) => { this.data = newData; this.serviceUpdated.emit(); }) } }
这样Angular就能感知到serviceUpdated事件的触发,以及后续组件内data的更新,自动执行变更检测。
3. 优化组件数据绑定方式(可选)
可以不用手动订阅serviceUpdated事件,而是让服务把data暴露为Observable,组件直接用AsyncPipe绑定,简化代码:
服务修改:
import { BehaviorSubject } from 'rxjs'; export class MyService { private dataSubject = new BehaviorSubject<string[]>([]); public data$ = this.dataSubject.asObservable(); // 暴露为Observable public constructor(private zone: NgZone) { this.updateService(); listen('data-updated', () => { this.zone.run(() => { this.updateService(); }); }) } private updateService() { forkJoin({ // ask backend to send new data }).subscribe(({newData}) => { this.dataSubject.next(newData); // 更新BehaviorSubject的值 }) } }
组件修改:
export class MyComponent { public data$ = this.myService.data$; // 直接获取Observable constructor(private myService: MyService) {} }
模板中使用AsyncPipe:
<ul> <li *ngFor="let item of data$ | async">{{ item }}</li> </ul>
AsyncPipe会自动订阅Observable,并且在数据更新时触发Angular变更检测,无需手动处理订阅和更新逻辑。
内容的提问来源于stack exchange,提问作者nutsalhan87

