首页 > 解决方案 > 使用 Subject.next() 方法通知订阅者后,不会触发角度变化检测过程

问题描述

在测试应用程序中,有一些组件在向所有订阅者发送通知时会更新数据。我使用 SHARED_STATE 注入令牌来引用能够同时提供 Observer 和 Observable 接口的 Subject 对象,因此我可以使用 next() / subscribe() 方法。

    providers: [{
    provide: SHARED_STATE,
    deps: [MessageService, Model],
    useFactory: (messageService: MessageService, model: Model) => {
        let subject = new Subject<SharedState>();
        subject.subscribe(m => messageService.reportMessage(
                new Message(m.mode === MODES.ERROR ? m.errorMsg : MODES[m.mode] + (m.id != undefined
                    ? ` ${model.getProduct(m.id).name}` : ""), m.mode === MODES.ERROR))
            );
        return subject;
    }
}]

在 ShareState 对象的 subscribe 方法中,我正在制作 Message 对象,该对象转到 messageService 的 reportMessage 方法:

@Injectable()
export class MessageService {
    private subject = new Subject<Message>();

    reportMessage(msg: Message) {
        this.subject.next(msg);
    }

    get messages(): Observable<Message> {
        return this.subject;
    }
}

应用程序使用消息组件显示消息:

    @Component({
      selector: "paMessages",
      template: `<div *ngIf="lastMessage"
      class="bg-info text-white p-2 text-center"
      [class.bg-danger]="lastMessage.error">
         <h4>{{lastMessage.text}}</h4>
    </div>`,
    })
    export class MessageComponent {
      lastMessage: Message;
    
      constructor(messageService: MessageService) {
        messageService.messages.subscribe(m => { this.lastMessage = m; (<any>window).m = this.lastMessage; });
      }
    }

接收 SHARE_STATE 通知的 next() 方法是从另一个组件调用的 => TableComponent:

@Component({
    selector: "paTable",
    templateUrl: "table.component.html"
})
export class TableComponent {

    constructor(private model: Model,
        @Inject(SHARED_STATE) public observer: Observer<SharedState>) { }

    editProduct(key: number) {
        this.observer.next(new SharedState(MODES.EDIT, key));
    }

    createProduct() {
        this.observer.next(new SharedState(MODES.CREATE));
    }
}

并且有来自自定义错误处理程序的调用:

@Injectable()
export class MessageErrorHandler implements ErrorHandler {

  constructor(private messageService: MessageService, private ngZone: NgZone, @Inject(SHARED_STATE) private state: Observer<SharedState>) {
  }

  handleError(error) {
    let msg = error instanceof Error ? error.message : error.toString();
    // this.ngZone.run(() =>
    this.state.next(new SharedState(MODES.ERROR, undefined, msg))
    // );
  }
}

当我按下创建/编辑产品按钮(来自 TableComponent 的 createProduct / editProduct 方法)时,角度触发更改检测过程并且消息组件的内容被重新呈现,但还有一个按钮专门创建以这种方式处理的 http 错误:

return this.http.request<T>(verb, url, {
    body: body,
    headers: myHeaders
}).pipe(catchError((error: Response) => 
     throwError(`Network Error: ${error.statusText} (${error.status})`)));

但是当我按下导致 http 错误的按钮时,消息组件没有被渲染/重新渲染,但是,我看到消息组件的 lastMessage 属性值发生了变化,当我在浏览器的控制台中访问这个变量时,我可以看到它: 在此处输入图像描述

当我开始在右侧的表单中选择输入区域时,Angular 会触发更改检测过程并重新呈现消息组件: 在此处输入图像描述

当我 this.state.next(new SharedState(MODES.ERROR, undefined, msg))将这行代码包装到 NgZone.run 方法中时,问题就解决了。我知道角度变化检测过程依赖于 Zone 库,该库修补 Web API 以提供生命周期钩子,并且角度定义了 ngZone 服务以基于这些钩子自动触发变化检测。

我想问的问题是:

1. 为什么创建/编辑具有类似功能的按钮在“角度”区域内执行,但错误处理代码需要我重新进入区域以自动触发更改检测?

2. 在编写代码时,我怎么知道这段代码将在“角度”区域内执行,何时不执行?

谢谢你。

标签: angularangular-changedetectionngzone

解决方案


推荐阅读