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?
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!
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.
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.
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).
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.
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));
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With