angular controller function once $q.defer() is complete - angularjs

I am new to angular and probably doing this completely wrong. On the /sites controller page, I want to access verifyAuth.getUserSiteAccess() to return a list of sites and build html links for the view.
I am using a google auth module, if the user logs in the userSites var is empty so I ping google, then call /api/index.php/login to return a list of user sites, then in this case finish with $q.defer().resolve(true);. Problem is the site controller function is trying to access userSites before it is defined. Is there a way to call $scope.test() after $q.defer().resolve is finished? or is there a better way to do this?
If I run setTimeout($scope.test, 500) it works fine.
Route -> Verify user access token, load userSites if undefined -> verify section access -> complete defer.
Site controller
'use strict';
angular.module('mps.sites', ['ngRoute'])
.controller('sites', ['verifyAuth', '$rootScope', '$scope', '$q', function(verifyAuth, $rootScope, $scope, $q) {
$scope.test = function() {
var test = verifyAuth.getUserSiteAccess();
console.log('test', test, '/test');
};
$scope.test();
}]);
** App.js routing and auth ** - not entire file...
'use strict';
angular.module('mps', [
'ngRoute',
'angularOauth',
'googleOauth',
'mps.global',
'mps.home',
'mps.sites',
'mps.site'
]).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'views/home/index.html',
controller: 'home',
resolve: {
auth: function(verifyAuth) {
verifyAuth.verifyUserAccess(true);
}
}
});
$routeProvider.when('/sites', {
templateUrl: 'views/sites/index.html',
controller: 'sites',
resolve: {
auth: function(verifyAuth) {
console.log('sites route selected');
verifyAuth.verifyUserAccess(false);
}
}
});
...
.factory('verifyAuth', ['$rootScope', '$window', '$q', '$location', '$timeout', '$http', 'Token',
function($rootScope, $window, $q, $location, $timeout, $http, Token) {
var userSites = null;
return {
deferLocation: function(isToken, index) {
var deferred = $q.defer();
var _location = $location;
if(isToken) {
switch(index) {
case true:
// Homepage/site toggling.
deferred.reject();
_location.path('/sites');
_location.replace();
break;
default:
// All pages.
deferred.resolve(true);
break;
}
} else {
// No token, redirect to login screen.
this.userError();
}
},
verifySectionAccess: function(userSites, siteName, index) {
if(siteName) {
// Subpage, verify section.
for(var i in userSites.sites) {
if(userSites.sites[i].sitename === siteName) {
this.deferLocation(true, index);
return false;
}
}
} else {
// Sites page.
this.deferLocation(true, index);
return false;
}
// No access to section.
this.userError();
return false;
},
// Check user permission are set.
verifyUserAccess: function (index, siteName) {
var token = Token.get();
var _this = this;
if(token) {
if(userSites) {
// Verify user section access.
_this.verifySectionAccess(userSites, siteName, index);
} else {
// Use google token to get user email and load site permissions.
Token.verifyAsync(token).
then(function(data) {
$http({method: 'GET', async: false, url: '/api/index.php/login/' + data.email}).success(function(d) {
userSites = d;
// Verify user access to specific section.
_this.verifySectionAccess(userSites, siteName, index);
});
}, function(error) {
_this.userError();
return false;
}
);
}
} else {
this.deferLocation(false, index);
}
},
getUserSiteAccess: function() {
console.log(userSites);
return userSites;
}

You have a number of issues here, all seem to be stemming from a misunderstanding of how promises work:
1) Functions that make async operations and are .then-able need to return a promise.
In your case, your deferLocation creates a promise (although doesn't return it), but it does not even do anything async.
On the other hand, the only function that does do something async (verifyUserAccess) doesn't have promises at all.
2) If you want a resolve parameter to be resolved with async value (as it seems to be with auth), then the function needs to return a promise. Then $route will wait until the promise is resolved. In your case, you don't return anything.
I suggest you read more about promises. Build something small with mock $timeout calls and ask specific questions if you run into problems.
Here's a high-level idea of how to build this:
app.factory("verifyAuth", function($q, Token, AuthSvc, SitesSvc) {
return {
verifyUserAccess: function(site){
var deferred = $q.defer();
var token = Token.token;
if (!token){
deferred.reject("no-token");
} else {
AuthSvc.verifyToken(token)
.then(function(result){
if (result.isValid){
// returns a promise to get a list of userSites
return SitesSvc.getSites(result.user);
} else {
return deferred.reject("token-not-valid");
}
})
.then(function(userSites){
if (checkAccessPermissions(site, userSites)){
deferred.resolve(true);
} else {
deferred.resolve(false);
}
})
.catch(function(error){
// some other error
deferred.reject(error);
});
}
return deferred.promise;
}
};
});

Router calls return verifyUser.buildUserData in the resolve, this checks if there is a token, if not logs the user out. Then checks if there is a site list global variable, if not pings the token to google to get the user email and the user email to the database to get the site list, from there loop through the list and check auth if a site name is passed into buildUserData.
The below example will handle all the auth before the view is rendered. Thanks New Dev for pointing me in the right direction.
Router:
...
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'views/home/index.html',
controller: 'home',
resolve: {
auth: function(verifyUser) {
return verifyUser.homepageRedirect();
}
}
});
$routeProvider.when('/sites', {
templateUrl: 'views/sites/index.html',
controller: 'sites',
resolve: {
auth: function(verifyUser) {
return verifyUser.buildUserData();
}
}
});
$routeProvider.when('/sites/:site/scheduling', {
templateUrl: 'views/sites/scheduling/index.html',
controller: 'site',
resolve: {
auth: function(verifyUser, $route) {
return verifyUser.buildUserData($route.current.params.site);
}
}
});
...
Factories
.factory('getUserEmail', ['$rootScope', '$window', '$q', '$location', '$timeout', '$http', 'Token', function($rootScope, $window, $q, $location, $timeout, $http, Token) {
return {
// Get user email address based on token.
getEmail: function() {
var deferred = $q.defer();
var token = Token.get();
$http({method: 'GET', async: false, url: 'https://www.googleapis.com/oauth2/v1/tokeninfo', params: {access_token: token }}).
success(function(data) {
$rootScope.username = data.email;
deferred.resolve(data);
return data;
}).error(function(data) {
deferred.reject(data);
});
return deferred.promise;
}
}
}])
.factory('getUserSites', ['$rootScope', '$window', '$q', '$location', '$timeout', '$http', 'Token', function($rootScope, $window, $q, $location, $timeout, $http, Token) {
return {
// Get user site access.
getSites: function() {
var deferred = $q.defer();
console.log('site list is undefined.');
$http({method: 'GET', async: false, url: '/api/index.php/login/' + $rootScope.username}).
success(function(data) {
$rootScope.sites = data.sites;
deferred.resolve(data);
}).error(function(data) {
deferred.reject(data);
});
return deferred.promise;
}
}
}])
.factory('verifyUser', ['$rootScope', '$window', '$q', '$location', '$timeout', '$http', 'Token', 'getUserEmail', 'getUserSites', function($rootScope, $window, $q, $location, $timeout, $http, Token, getUserEmail, getUserSites) {
return {
siteError: function() {
localStorage.removeItem('accessToken');
$location.path('/');
},
// Redirect user to /sites if logged in.
homepageRedirect: function() {
var deferred = $q.defer();
var token = Token.get();
if(!token) {
deferred.resolve(true);
} else {
deferred.reject(true);
$location.path('/sites');
}
return deferred.promise;
},
// Verify user token exists and they have access to the section.
buildUserData: function(site) {
console.log('site',site,'/site');
var deferred = $q.defer();
var token = Token.get();
var _this = this;
if(!token) {
deferred.reject('no token');
localStorage.removeItem('accessToken');
$location.path('/');
} else {
if($rootScope.sites) {
console.log('site list present, check access.');
if(site) {
var data = $rootScope.sites;
console.log(data, site);
for(var i in data) {
console.log(data[i].sitename);
if(data[i].sitename === site) {
console.log('user has access, render view.');
deferred.resolve(true);
return false;
}
}
console.log('No site access, logout.');
deferred.reject('No access to site.');
_this.siteError();
} else {
console.log('No access needed, landing page.');
deferred.resolve(true);
}
} else {
console.log('no site list, get user email from google and query db with user.');
getUserEmail.getEmail().then(function(data) {
return getUserSites.getSites();
}).then(function(data) {
if(site) {
console.log('sub page');
for(var i in data.sites) {
console.log(data.site[i]);
if(data.sites[i].sitename === site) {
console.log('user has access, render view.');
deferred.resolve(true);
return false;
}
}
console.log('No site access, logout.');
deferred.reject('No access to site.');
_this.siteError();
} else {
deferred.resolve(true);
}
}).catch(function(data) {
deferred.reject(true);
_this.siteError();
});
}
}
return deferred.promise;
}
}
}]);

Related

Promise returning object in angular js

i have my authservice as given below ,
myApp.factory('Authentication',
['$rootScope', '$location', 'URL', '$http', '$q',
function ($rootScope, $location, URL, $http, $q) {
var myObject = {
authwithpwd: function (user) {
var dfd = $q.defer();
$http
.post('Mart2/users/login', {email: user.email, password: user.password})
.then(function (res) {
return dfd.resolve(res.data);
}, function (err) {
return dfd.reject(err.data);
});
return dfd.promise;
} //login
};
return myObject;
}]); //factory
And i'm using that service in user service as follows :
myApp.factory('UserService',
['$rootScope', '$location', 'URL', '$http', '$q', 'Authentication',
function ($rootScope, $location, URL, $http, $q, $Authentication) {
var myObject = {
login: function (user) {
$Authentication.authwithpwd(user).then(function (regUser) {
console.log(regUser);
}).catch(function (error) {
$rootScope.message = error.message;
});
},
getUserToken: function () {
return $rootScope.currentUser.apiKey;
},
isLogged: function () {
if ($rootScope.currentUser) {
return true;
} else {
return false;
}
}
//login
};
return myObject;
}]); //factory
I am very new to angular js . While writing service and calling that service from controller i have put a console debug in user service which is showing its returning object .i am getting object if i do console.log(regUser) ? any idea why ?
To get the object you need to do change your myObject declaration. Basically you need to return a promise from the login function and then write a callback to get the resolved data.
myApp.factory('UserService',
['$rootScope', '$location', 'URL','$http','$q','Authentication',
function($rootScope,$location, URL,$http,$q,$Authentication) {
var myObject = {
login: function(user) {
var defer = $q.defer();
$Authentication.authwithpwd(user).then(function(regUser) {
console.log(regUser);
defer.resolve(regUser);
}).catch(function(error) {
$rootScope.message = error.message;
defer.reject(regUser);
});
return defer.promise;
},
getUserToken:function() {
return $rootScope.currentUser.apiKey;
},
isLogged:function() {
if($rootScope.currentUser){
return true;
} else {
return false;
}
}//login
};
return myObject;
}]); //factory
To extract the object from controller or from some other service you need to write a callback
UserService.login(user)
.then(function (data) {
$scope.data = data;
}, function (error) {
$scope.error = error;
});
Also in the Authentication service you can just do a 'dfd.resolve' instead of 'return dfd.resolve'; since you are already returning the dfd.promise.
I have created a fiddler here

Facebook SDK not loaded during the call to get login status

I am using Facebook SDK to build an application however I am faced with a challenge, I try to get the login status of the user before I redirect him to his profile page, however during the call to get the login status I get the error that
ReferenceError: FB is not defined
now the SDK is being loaded asynchronously so the error makes sense, how can i resolve the problem. Here is my code:
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: "Views/ListPages.html",
controller: 'MainCtrl',
resolve: {
authentication:["$location", "LoginFactory", function($location, LoginFactory){
console.log("in resolve");
LoginFactory.getLoginStatus()
.then(function(response){
if(response){
$location.path('/login');
}
else{
$location.path('/');
}
});
}]
}
})
.when('/login', {
templateUrl: "Views/Login.html",
controller: 'LoginCtrl'
})
.otherwise
({redirectTo: '/'});
});
loginApp.factory("LoginFactory", function ($rootScope, $q, $location, UserInfo) {
return {
getLoginStatus: function () {
var deferred = $q.defer();
FB.getLoginStatus(function (response) {
if(!response || response.error){
deferred.reject(new error("User Not logged in."));
}
else{
deferred.resolve(response);
}
});
return deferred.promise;
},
login: function () {
FB.login(function (response) {
if (response.authResponse === "connected") {
$rootScope.$broadcast('fb_connected', {facebook_id:response.authResponse.userID});
} else {
$rootScope.$broadcast('fb_login_failed');
}
}, {scope: "read_insights, publish_pages, manage_pages"});
},
logout: function () {
FB.logout(function (response) {
if (response) {
$rootScope.$broadcast('fb_logout_succeded');
$location.path('/login');
} else {
$rootScope.$broadcast('fb_logout_failed');
}
});
}
};
angular.module("LoginCtrlModule", ["FacebookLogin"])
.controller("LoginCtrl", ["$scope", "$location", "LoginFactory", function ($scope, $location, LoginFactory) {
$scope.login = function () {
LoginFactory.login();
$scope.on("fb_connected", function () {
$location.path("/");
});
$scope.on("fb_login_failed", function(){
$location.path("/login");
});
}
}]);
app.run(function ($rootScope, $location, LoginFactory) {
window.fbAsyncInit = function () {
FB.init({
appId: '',
status: true,
cookie: true,
xfbml: true
});
};
(function (d) {
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement('script');
js.id = id;
js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
}(document));
});
I was following these posts:
AngularJS- Login and Authentication in each route and controller
http://www.sitepoint.com/implementing-authentication-angular-applications/
But the problem i am facing is different.
Thanks for the help.
I think the solution is to not call the FB.getLoginStatus() but rather have an object that contains the User information and the access token, each time I try to route I should check if the User info object is null, in case if its not null then call the check login status method and accordingly take the route or not to take the route.

Angularjs service doesn't work in run() method

I got a simple get and set service in my angular app, that stores data from a $http request, but for some reason it just doesn't seem to work in angular's run() method. I'm not sure what am doing wrong.
my service
app.factory('sessionService', function() {
var user_info = {};
return {
set: function(value) {
user_info = value;
},
get: function() {
return user_info;
}
};
});
my run method
app.run(['$rootScope', '$location', 'Auth', 'sessionService',
function($rootScope, $location, Auth, sessionService) {
var routespermission = ['/dashboard', '/create']; //route that require login
$rootScope.$on('$routeChangeStart', function() {
if (routespermission.indexOf($location.path()) != -1) {
Auth.check({
type: 'checkSession'
}).success(function(data) {
if (data.status === false) {
$location.path('/user/login');
} else {
sessionService.set(data);
}
});
}
});
}
]);
trying to access the data in my controller
app.controller('dashboardCtrl', ['$scope', '$location', 'sessionService',
function($scope, $location, sessionService) {
$scope.user_info = sessionService.get();
console.log($scope.user_info);
$scope.create_review = function() {
}
}
]);
when I console log the service in my controller, it return an empty object. I don't getany error, so not sure where i went wrong
you should inject $route dependency in app.run for '$routeChangeStart' to work.
app.run(['$rootScope', '$location','$route', 'Auth', 'sessionService',
function($rootScope, $location,$route, Auth, sessionService) {
var routespermission = ['/dashboard', '/create']; //route that require login
$rootScope.$on('$routeChangeStart', function() {
if (routespermission.indexOf($location.path()) != -1) {
Auth.check({
type: 'checkSession'
}).success(function(data) {
if (data.status === false) {
$location.path('/user/login');
} else {
sessionService.set(data);
}
});
}
});
}
]);

angularjs change service property from $http.get

The following code should check if a user is logged in and "save" the response in the accountService.
The commented line (//accountService.currentUser = {username : 'test'};) worked but as soon as i alter the service property from within the $http callback it doesn't work. So what am I missing?
app.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'partials/portal/index.html',
resolve: {
loggedin: isLoggedIn
}
})
var isLoggedIn = function($q, $timeout, $http, $location, accountService){
var deferred = $q.defer();
//accountService.currentUser = {username : 'test'};
$http.get('/api/account/isLoggedIn').success(function(user){
if (user) {
$timeout(function () {
accountService.currentUser = user;
deferred.resolve();
}, 0);
} else {
accountService.currentUser = null;
$timeout(deferred.reject, 0);
$location.path('/login');
}
});
};
}

Angularjs resource handling response & custom methods

Is it possible within the AuthService to handle the response and set the session with the SessionService?
I doing it in the controller right now with the success callback but I'm still new to Angularjs and trying to understand how to customize a resource.
I'm using Angularjs 1.1.5
app.factory('AuthService', ['$resource', 'SessionService',
function($resource, SessionService) {
return $resource(
'/api/v1/auth/:action',
{action:'#action'},
{
login: {
method:'POST',
params: {
action: 'login'
}
},
logout: {
method:'GET',
params: {
action: 'logout'
}
}
}
);
}
]);
app.controller('LoginCtrl', ['$scope', '$location', 'AuthService', 'SessionService' function LoginCtrl($scope, $location, AuthService, SessionService) {
$scope.credentials = { email: "", password: ""};
$scope.login = function() {
AuthService.login($scope.credentials).success(function() {
SessionService.set('authenticated', true);
$location.path('/home');
});
}
}]);
app.factory("SessionService", function() {
return {
get: function(key) {
return sessionStorage.getItem(key);
},
set: function(key, val) {
return sessionStorage.setItem(key, val);
},
unset: function(key) {
return sessionStorage.removeItem(key);
}
}
});
I'm not sure that $resource is the appropriate abstraction to use here. I think it would be much simpler to implement your AuthService using plain $http. Just implement login and logout as normal function, then you can feel free to do whatever you want there. You should also make sure you return the promise, that way whoever calls login() or logout() can still do .then() on it if they need to do additional things after login. Here's an example:
app.factory('AuthService', ['$http', '$location' 'SessionService',
function($http, $location, SessionService) {
var baseUrl = '/api/v1/auth/';
function onLoginSuccess(data){
SessionService.set('authenticated', true);
$location.path('/home');
}
function onLoginFailure(error){
SessionService.unset('authenticated');
$location.path('/login');
}
return {
login: function(credentials){
return $http.post(baseUrl+'login', credential).then(onLoginSuccess, onLoginFailure);
}
logout: function(){
return $http.get(baseUrl+'logout');
}
};
app.controller('LoginCtrl', ['$scope', 'AuthService', function LoginCtrl($scope, AuthService) {
$scope.credentials = { email: "", password: ""};
$scope.login = function() {
AuthService.login($scope.credentials);
}
}]);
app.factory("SessionService", function() {
return {
get: function(key) {
return sessionStorage.getItem(key);
},
set: function(key, val) {
return sessionStorage.setItem(key, val);
},
unset: function(key) {
return sessionStorage.removeItem(key);
}
}
});
Your server side script on path /api/v1/auth/login should return a result to indicate that the login is successfully granted or not.
For example, If the log in is granted, then /api/v1/auth/login returns the success response 200.
If the login is denied, then /api/v1/auth/login returns the bad request response (failure) 400.
If with this condition, your code should be written as followed,
app.controller('LoginCtrl', ['$scope', '$location', 'AuthService', 'SessionService'
function LoginCtrl($scope, $location, AuthService, SessionService) {
$scope.credentials = { email: "", password: ""};
$scope.login = function() {
AuthService.login($scope.credentials, function(data) { //Success callback
SessionService.set('authenticated', true);
$location.path('/home');
}, function(error){ //Failure callback
SessionService.unset('authenticated');
$location.path('/login');
});
}
}]);

Resources