Reputation: 4525
I have a project that uses angular's $http service to load data from a remote location. I want to use rxjs Observables so the call in my service looks like this:
userInfo() : Rx.Observable<IUserInfo> {
var url : string = someUrl + this._accessToken;
return Rx.Observable.fromPromise<IUserInfo>( this.$http.get<IUserInfo>( url ) );
}
and this is subscribed to by my controller like this:
getUserInfo() : void {
this._googleService.userInfo().subscribe(
( result ) => { this.handleUserInfo( result ) },
( fault : string ) => this.handleError( fault )
)
}
private handleUserInfo( result : IHttpPromiseCallbackArg<IUserInfo> ) : void {
console.log( "User info received at " + new Date() );
this._name = result.data.given_name + " " + result.data.family_name;
this._email = result.data.email;
this._profilePicUrl = result.data.picture;
}
the problem is that despite the name, email and profile pic being updated these changes are not visible. As soon as anything else triggers an angular $apply the changes appear but because of the Observable these changes in the controller happen after the angular digest loop that is triggered by the $http call. This does work correctly if my service just returns a promise to the controller.
How do I update my view in this case? I do not want to manually have to wire up each observable to trigger a digest cycle. I want all Observables to trigger a digest cycle when they receive a new value or error.
Upvotes: 4
Views: 948
Reputation: 3495
I couldn't get the Rx.ScopeScheduler method to work, so I just overwrote the rx observable subscribe method itself instead, and wrapped the callbacks in $rootScope.$apply :)
module.run(['$rootScope', 'rx', function ($rootScope, rx) {
rx.Observable.prototype.subscribe = function (n, e, c) {
if(typeof n === 'object') {
return this._subscribe(n);
}
var onNext = function(){};
if(n) {
onNext = function(value) {
if($rootScope.$$phase) {
n(value);
}
else {
$rootScope.$apply(function(){ n(value); });
}
};
}
var onError = function(err) { throw err; };
if(e) {
onError = function(error) {
if($rootScope.$$phase) {
e(error);
}
else {
$rootScope.$apply(function(){ e(error); });
}
};
}
var onCompleted = function(){};
if(c) {
onCompleted = function() {
if($rootScope.$$phase) {
c();
}
else {
$rootScope.$apply(function(){ c(); });
}
};
}
return this._subscribe(
new rx.AnonymousObserver(onNext, onError, onCompleted)
);
};
}]);
Upvotes: 0
Reputation: 4525
We can use the ScopeScheduler from rx.angular.js for this. We only have to create a new one where we create our angular module and pass the $rootScope to it:
const module : ng.IModule = angular.module( 'moduleName', [] );
module.run( ["$rootScope", ( $rootScope ) => {
new Rx.ScopeScheduler( $rootScope );
}]);
That's all you have to do. Now all Rx.Observables trigger an $apply when they get a new value.
For some reason the ScopeScheduler was deleted when the rx.angular.js library was upgraded to rxjs version 4. We have to use rx.angular.js version 0.0.14 to use the ScopeScheduler.
I do not know what the suggested solution to this is in version 4.
A project using this fix can be viewed here:
https://github.com/Roaders/Typescript-OAuth-SPA/tree/observable_apply_issues
Upvotes: 3