Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I "monkey patch" an Observable for Zone.js?

I'm creating an Angular 2 component, and Angular's change detection isn't working for me when using a certain Observable pattern. It looks something like this:

    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);

Then in my template:

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

Since the readyToLogin$ Observable is based on a synchronous set of operations that happen in response to the http.get() (which Angular "monkey patches" to ensure it knows when it needs to detect changes), the "Ready to Log In" message switches to true at the appropriate time.

However, since processSignInResponse() produces a Promise<>, anything subscribing to the result of flatMap is occurring asynchronously to the http request's completion event. Therefore, it requires manual intervention to notify my component's zone that it needs to check for changes.

How can I wrap the signInResponse$ observable in a way that NgZone knows to detect changes after any subscriptions have been resolved?

Update

Brandon's answer worked until I updated to RC5, at which point things stopped working again. Turns out the 3rd-party library I was using borked Zone.js. Once that was resolved, there was no need to use a workaround at all--the built-in monkey patching just worked!

like image 504
StriplingWarrior Avatar asked Jun 01 '16 22:06

StriplingWarrior


People also ask

What is monkey patching in Angular?

By definition, Monkey patching is basically extending or modifying the original API. Now, zone. js re-defines all the async APIs like browser apis which includes set/clearTimeOut, set/clearInterval, alert, XHR apis etc. Now, whenever we call any api like below in our angular application, window.

Is Zone js required for Angular?

Angular uses Zone. js, Yes. But, Angular doesn't encapsulate the whole framework. It only leverages the execution context to detect changes and async events so it could trigger UI update.

How does Zone js work in Angular?

js provides a mechanism, called zones, for encapsulating and intercepting asynchronous activities in the browser (e.g. setTimeout , , promises). These zones are execution contexts that allow Angular to track the start and completion of asynchronous activities and perform tasks as required (e.g. change detection).

What is NgZone used for?

A zone is an execution context that persists across async tasks. You can think of it as thread-local storage for JavaScript VMs. This guide describes how to use Angular's NgZone to automatically detect changes in the component to update HTML.


2 Answers

For RxJs 6 with the pipable operator:

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);
    });
  };
}

Usage:

$someObservable.pipe(runInZone(zone));
like image 185
Robin Dijkhof Avatar answered Oct 19 '22 10:10

Robin Dijkhof


You can make a new observeOnZone operator that can be used to "monkey patch" any observable. Something like:

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);
    });
};

And use it like so:

this.isLoggedIn$ = signinResponse$.map(() => true).startWith(false).observeOnZone(zone);
like image 23
Brandon Avatar answered Oct 19 '22 08:10

Brandon