在测试应用程序中,有一些组件在向所有订阅者发送通知时会更新数据。我使用 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. 在编写代码时,我怎么知道这段代码将在“角度”区域内执行,何时不执行?
谢谢你。