【问题标题】:How can I "monkey patch" an Observable for Zone.js?我如何为 Zone.js “猴子补丁”一个 Observable?
【发布时间】:2016-10-01 11:52:50
【问题描述】:

我正在创建一个 Angular 2 组件,当使用某种可观察模式时,Angular 的更改检测对我不起作用。它看起来像这样:

    let getResult$ = this.http.get('/api/identity-settings');

    let manager$ = getResult$
        .map((response) => /* -- create manager object -- */);

    let signinResponse$ = manager$
        .flatMap(manager => manager.processSigninResponse());

    this.readyToLogin$ = manager$.map(() => true).startWith(false);
    this.isLoggedIn$ = signinResponse$.map(() => true).startWith(false);

然后在我的模板中:

<h1>Ready to Log In: {{readyToLogin$ | async}}</h1>
<h1>Logged In: {{isLoggedIn$ | async}}</h1>

由于readyToLogin$ Observable 基于一组同步操作,这些操作是为了响应http.get()(Angular “猴子补丁”以确保它知道何时需要检测更改)而发生的,因此“准备记录In”消息在适当的时候切换到true

但是,由于processSignInResponse() 产生Promise&lt;&gt;,任何订阅flatMap 结果的事件都与http 请求的完成事件异步发生。因此,它需要手动干预来通知我的组件的区域它需要检查更改。

如何以NgZone 知道在解决任何订阅后检测更改的方式包装signInResponse$ observable?

更新

Brandon 的回答一直有效,直到我更新到 RC5,此时一切都停止了。结果是the 3rd-party library I was using borked Zone.js。一旦解决了这个问题,就根本不需要使用解决方法——内置的猴子补丁就可以了!

【问题讨论】:

  • Promise 通常不会导致 NgZone 出现问题。我已经看到它在Promise 从错误的库导入时会导致此类问题的问题中提到(我正在使用 Dart,但不知道有关 polyfill 和 TS/JS 的东西的详细信息)。此外,如果发出可观察事件的代码以某种方式在 Angulars 区域之外运行,那么更改检测就会以这种方式中断。
  • @GünterZöchbauer:据我了解,Promises 通常会通过一些已经“猴子修补”以与 Zone.js 一起使用的事件来解决。例如,一个 Angular HTTP 请求或一个 DOM 事件处理程序触发,导致一个 Promise 被解析,并且在解析该 Promise 的同步过程中,发生的任何动作都发生在区域运行的上下文中。但是我正在使用的这个库必须在某种尚未被猴子修补的事件之后解决它的承诺。
  • 是的,类似的。
  • @StriplingWarrior 你的编辑是什么意思?角度的内置行为刚刚起作用吗?角度怎么可能知道?没有意义
  • @Ced:Angular 依赖于 Zone.js,它使用 polyfill 来检测浏览器事件何时会导致更改发生。当我以覆盖这些 polyfill 的方式导入库时,Zone 无法检测到其中一个事件何时发生。当我解决这个问题时,AJAX 响应被 Zone 正确检测到,这告诉 Angular 发生了一些事情。

标签: angular rxjs zonejs


【解决方案1】:

对于带有 pipable 操作符的 RxJs 6:

private runInZone(zone) {
  return function mySimpleOperatorImplementation(source) {
    return Observable.create(observer => {
      const onNext = (value) => zone.run(() => observer.next(value));
      const onError = (e) => zone.run(() => observer.error(e));
      const onComplete = () => zone.run(() => observer.complete());
      return source.subscribe(onNext, onError, onComplete);
    });
  };
}

用法:

$someObservable.pipe(runInZone(zone));

【讨论】:

  • 非常好。只需导出此函数并像普通管道运算符一样使用它。正是我想要的。谢谢!
  • 这应该是已接受的答案,因为它与已接受的答案相同,但使用新的 rxjs api 是最新的
【解决方案2】:

您可以创建一个新的observeOnZone 运算符,用于“猴子补丁”任何可观察的。比如:

Rx.Observable.prototype.observeOnZone = function (zone) {
    return Observable.create(observer => {
        var onNext = (value) => zone.run(() => observer.next(value));
        var onError = (e) => zone.run(() => observer.error(e));
        var onComplete = () => zone.run(() => observer.complete());
        return this.subscribe(onNext, onError, onComplete);
    });
};

然后像这样使用它:

this.isLoggedIn$ = signinResponse$.map(() => true).startWith(false).observeOnZone(zone);

【讨论】:

  • 谢谢!这最终进行了一些修改。首先,我修复了您的 observeOnZone 方法以调用观察者的 .next().error() 等而不是 .onNext()onError() 等(我编辑了您的帖子以显示此修复)。其次,因为在我的实际用例中,signInResponse$ 是在任何角度组件之外的服务中创建的,所以我需要一种方法来在当前丢失的点处捕获适当的区域:.flatMap(manager =&gt; Observable.fromPromise(manager.processSigninResponse()).observeOnZone(Zone.current))
  • 感谢您的修复。我没有使用过 rxjs5,所以我通常会忘记他们对 API 所做的更改。
  • 对于新的管道语法请参阅stackoverflow.com/a/50583597/6656422
【解决方案3】:

您可以使用 zone.run() 强制代码进入 Angulars 区域

constructor(private zone:NgZone) {}

someMethod() {
  signinResponse$.subscribe(value => {
    zone.run(() => doSomething());
  });
}

【讨论】:

  • 是的,我知道这么多。问题是,我不想从我的组件代码中订阅 signInResponse$:我希望 UI 中的异步管道绑定能够正常工作,因为 observable 正在正确的区域中解析。
  • 只是想知道:如果 signinResponse 是一个 HTTP 请求 - 仍然需要 zone.run 吗?
  • @RoyiNamir 仅取决于代码是否在区域内运行。通常你根本不需要 zone.run,但如果代码以某种方式在 Angulars 区域之外运行,你需要将它带回来以进行更改检测。 (ChangeDetectorRef.detectChanges 也可以,视具体情况而定)
猜你喜欢
  • 2011-10-06
  • 2020-05-24
  • 2016-09-01
  • 2012-09-16
  • 2012-12-18
  • 2020-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多