I am new to angular, and I am having a tough time getting to the bottom of this problem.
I am writing a single-page application, and am working on the authentication portion. I have a service called "sessionService" that I want to be able to use throughout the app to determine if the user is logged in or not. It is simple if I do something like this:
...service('sessionService', function(...) {
/*...snip...*/
this.isLoggedIn = function() {
return this.authenticated;
};
});
Where "authenticated" is just private to the service. However, the falls apart if I refresh the page. So, my thought was to do something like this:
/*...snip...*/
this.isLoggedIn = function() {
var deferred = $q.defer()
, self = this
;
function handleLoggedInStatus(status) {
if (status) {
self.authenticated = true;
deferred.resolve();
}
else {
deferred.reject();
}
}
if (this.authenticated === null) {
$http.get('/user')
.success(function(response) {
handleLoggedInStatus(response.success);
});
}
else {
handleLoggedInStatus(this.authenticated);
}
return deferred.promise;
};
And then in my controller I would do something like this:
$scope.isLoggedIn = sessionService.isLoggedIn;
And in my template I would do:
...data-ng-show="isLoggedIn()"
However, doing that would result in the following error:
10 $digest() iterations reached. Aborting!
I tried a few different ways of referencing the sessionService.isLoggedIn function, such as:
$scope.isLoggedIn = sessionService.isLoggedIn();
$scope.isLoggedIn = sessionService.isLoggedIn.bind(sessionService)();
$scope.isLoggedIn = function() { return sessionService.isLoggedIn() }
But they either didn't work, or just gave me the same error.
Basically, I just want to be able to return a promise that will tell me whether or not the user is logged in. If we don't know if they are logged in (like after a page refresh), the promise will be resolved after an ajax request. If we do know already (like with normal navigation throughout the single page app) then the promise will be resolved immediately. I would then like to use that in my views so I can show/hide certain things, such as links to logout or view the account page.
What am I doing wrong?
What Is Promise in Angular? Promises in Angular provide an easy way to execute asynchronous functions that use callbacks, while emitting and completing (resolving or rejecting) one value at a time. When using an Angular Promise, you are enabled to emit a single event from the API.
resolve() method in JS returns a Promise object that is resolved with a given value. Any of the three things can happened: If the value is a promise then promise is returned. If the value has a “then” attached to the promise, then the returned promise will follow that “then” to till the final state.
Promises can be executed by calling the then() and catch() methods. The then() method takes two callback functions as parameters and is invoked when a promise is either resolved or rejected. The catch() method takes one callback function and is invoked when an error occurs.
Define an async function. Use the await operator to await the promise. Assign the result of using the await operator to a variable.
You're resolving your promise, but not with a value--so the value of the promise on the $scope
when resolved is undefined
, which is falsy, thus your ng-show
is not triggering.
It seems you're looking for something more like this:
In the service:
function handleLoggedInStatus(status) {
if (status) {
self.authenticated = true;
}
deferred.resolve(status); // always resolve, even if with falsy value
}
if (this.authenticated === null) {
$http.get('/user')
.success(function(response) {
handleLoggedInStatus(response.success);
})
.error(function(data) {
deferred.reject(data.errorMsg); // reject if there was an error
});
} else {
handleLoggedInStatus(this.authenticated);
}
In the controller:
$scope.loggedIn = sessionService.isLoggedIn();
In the HTML:
<div ng-show='loggedIn'>...</div>
Here is a JSFiddle demonstrating resolving the deferred with a truthy value and binding to the $scope
.
Note that you can't bind the function itself to the scope
$scope.loggedIn = sessionService.isLoggedIn
and call the function in the view
<div ng-show="loggedIn()">...</div>
because the function returns a different promise each digest cycle (which is why you were getting the '10 digest cycles' error). You could, however, ensure that extra calls to sessionService.isLoggedIn
returns the same promise instead of creating a new one, since you can call then
on a promise multiple times (and in fact this is one of the benefits of promises):
deferred = null;
isLoggedIn: function() {
if (!deferred) {
deferred = $q.defer();
$http.get('/user')
.success(function(response) {
deferred.resolve(response.success); // resolve if true or false
})
.error(function(data) {
deferred.reject(data.errorMsg); // reject if there was an error
});
}
return deferred.promise;
}
You could then get rid of the this.authenticated
boolean, as you do not need to keep track of a previously-logged-in user across function calls (since the promise does this for you).
However, while this gets rid of the digest cycle error, you still cannot call the function from the view--I suspect Angular is treating the return value (the promise itself) as a truthy value, rather than binding to the promise's resolved value. Here's an example of it not working; notice the div
is displayed even though the promise is resolving with false
.
To use deferred.reject
to indicate the user was not authenticated, as in your original service, you'd want to do something more like this in the controller, though I believe that resolve
ing with false
is cleaner:
sessionService.isLoggedIn()
.then(function() {
$scope.loggedIn = true; // resolved
}).then(function() {
$scope.loggedIn = false; // rejected
});
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