I have an AngularJS app which communicates with a Laravel PHP backend. Sending a GET request to /api/checkLogin will return { logged: false, username: undefined, id: undefined } if the user is not logged in, otherwise, it will return something like { logged: true, username: 'John', id: 123 }.
I am not too familiar with using AngularJS services, but I would like to set up a service called AuthService that can, well, perform my app's authentication services.
I would like to implement the following functions: AuthService.loggedIn, AuthService.isAdmin, AuthService.username, and AuthService.id.
I want these functions implemented in such a way that calling one will set the values for all the rest. For example, let's say I call AuthService.isAdmin. This function will check if isAdmin is set, if so, it will return the value of isAdmin. If isAdmin is not set, it will make an HTTP request to /api/checkLogin, set the values for loggedIn, isAdmin, username, and id, and then return the value of isAdmin. How can I accomplish this?
Here is the service I have tried putting together:
angular.module('myApp').factory('AuthService', ['$http', function($http) {
var loggedIn;
var isAdmin;
var username;
var id;
var checkLogin = function() {
if(loggedIn != undefined) {
return loggedIn
} else {
setUserData(checkLogin);
}
}
var checkAdmin = function() {
if(isAdmin != undefined) {
return isAdmin
} else {
setUserData(checkLogin);
}
}
var returnUsername = function() {
if(username != undefined) {
return username
} else {
setUserData(checkLogin);
}
}
var returnId = function() {
if(id != undefined) {
return id
} else {
setUserData(checkLogin);
}
}
// Our function call which will set our loggedIn, isAdmin, username, and id values
var setUserData = function(callback) {
$http.get(baseURL+'/api/checkLogin').success(function(data) {
loggedIn = data.logged;
if(loggedIn) {
isAdmin = data.is_admin;
username = data.username;
id = data.id;
}
callback();
});
}
return {
loggedIn: function() { return checkLogin(); },
isAdmin: function() { return checkAdmin(); },
username: function() { return returnUsername(); },
id: function() { return returnId(); },
}
}]);
It looks you want to use checkLogin as a callback, but instead of doing it the way you have it, return the promise back to checkLogin from setUserData. Then in checkLogin, create your own deferred to handle the results.
You are acting on asynchronous logic by introducing the $http call, so checkLogin is going to need to return a promise in all cases:
var checkLogin = function() {
// Create a custom deferred
var defer = $q.defer();
if(loggedIn != undefined) {
// Resolve your deferred with the value of logged in
defer.resolve(loggedIn);
} else {
setUserData().then(function (data) {
console.log('Set user data returned successfully');
loggedIn = data.logged;
if(loggedIn) {
isAdmin = data.is_admin;
username = data.username;
id = data.id;
defer.resolve(loggedIn);
} else {
defer.reject();
}
}, function () {
console.log('setUserData failed');
defer.reject();
});
}
return defer.promise;
}
var setUserData = function() {
return $http.get(baseURL+'/api/checkLogin');
}
AuthService.loggedIn() will now return a promise. You have to resolve the promise to get the value out of it:
AuthService.loggedIn().then(function (data) {
console.log(data);
});
The function passed to then above will be called when the promise is resolved with the value that the promise was resolved with. In this case, its the value of data.logged because thats what was passed to defer.resolve in your checkLogin function.
Here's some reading on Promises:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
http://www.html5rocks.com/en/tutorials/es6/promises/
https://docs.angularjs.org/api/ng/service/$q
use this
loginService this
var app=angular.module('myApp');
app.factory('loginService', function ($http, $location) {
return {
login: function (data, scope) {
var $promise = $http.post('api.php/site/login', data);
$promise.then(function (msg) {
var uId = msg.data.key;
if (msg.data.key) {
$location.path('/abAdmin/home');
} else {
$location.path('/abAdmin');
}
});
},
logout: function () {
$http.post('api.php/site/logout');
$location.path('/abAdmin');
},
isLogged: function () {
var $check = $http.post('api.php/site/checkSession');
return $check;
}
}
});
and your app.js
var app=angular.module('myApp');
app.run(function ($rootScope, $location, loginService) {
var routPermission = [
'/abAdmin/home',
'/abAdmin/category',
'/abAdmin/category/:id'];
$rootScope.$on('$routeChangeStart', function (e, current) {
if ( routPermission.indexOf(current.$$route.originalPath) != -1) {
var connected = loginService.isLogged();
connected.then(function (data) {
if (!data.data.isLogged) {
logged=true;
$location.path('abAdmin');
}
})
}
});
});
Related
I try to make facebook registration module in my app. Facebook API is faster than my Angular controller, so promise should be used here. The problem is that $q seems to be an empty object and defer function is undefined.
module:
var module = angular.module('app.facebook', []);
module.constant("fbAppId", 'herecomesmycode');
module.factory('facebook', FacebookAPI);
FacebookAPI.$inject = ['$ionicLoading', '$q', '$ionicPlatform', '$state', 'authService', 'datacontext', '$location'];
function FacebookAPI(UserService, $q, $ionicLoading, fbAppId, $state, authService, datacontext, $location) {
return {
fbLoginSuccess: fbLoginSuccess,
fbLoginError: fbLoginError,
getFacebookProfileInfo: getFacebookProfileInfo,
fbLogin: fbLogin,
fbRegister: fbRegister
};
and here $q.defer is undefined:
function fbRegister() {
console.log($q.defer);
if (!cordova) {
facebookConnectPlugin.browserInit(fbAppId);
}
var data;
facebookConnectPlugin.getLoginStatus(function (response) {
if (response.status !== 'connected') {
facebookConnectPlugin.login(["email"],
function(response) {
data = getApiData();
},
function(response) {
});
} else {
data = getApiData();
}
});
}
Without using promise, it gets fast from API but all variables I want to fill with values from API, are initiated before API finishes and are undefined.
The whole module:
(function() {
'use strict';
var module = angular.module('app.facebook', []);
module.constant("fbAppId", 'myappkey');
module.factory('facebook', FacebookAPI);
FacebookAPI.$inject = ['$ionicLoading', '$ionicPlatform', '$state', 'authService', '$q'];
function FacebookAPI(UserService, $ionicLoading, fbAppId, $state, authService, $q) {
return {
fbLoginSuccess: fbLoginSuccess,
fbLoginError: fbLoginError,
getFacebookProfileInfo: getFacebookProfileInfo,
fbLogin: fbLogin,
fbRegister: fbRegister
}
function fbRegister() {
console.log($q);
if (!cordova) {
facebookConnectPlugin.browserInit(fbAppId);
}
var data;
facebookConnectPlugin.getLoginStatus(function (response) {
if (response.status !== 'connected') {
facebookConnectPlugin.login(["email"],
function(response) {
data = getApiData();
},
function(response) {
});
} else {
data = getApiData();
}
});
}
function getApiData() {
var formData = {};
facebookConnectPlugin.api("me/?fields=id,first_name,last_name,link,gender,email,birthday", ["public_profile", "email", "user_birthday"],
function (result) {
if (result.gender == "male") {
result.gender = '1';
} else {
result.gender = '2';
}
formData = {
name: result.first_name + " " + result.last_name,
email: result.email,
birthday: new Date(result.birthday),
gender: result.gender
}
console.log("moduĊ" + formData);//here we have nice and neat data
return formData;
}, function(res) {
});
}
};
//This is the success callback from the login method
function fbLoginSuccess(response) {
var fbLogged = $q.defer();
if (!response.authResponse) {
fbLoginError("Cannot find the authResponse");
return;
}
var expDate = new Date(
new Date().getTime() + response.authResponse.expiresIn * 1000
).toISOString();
var authData = {
id: String(response.authResponse.userID),
access_token: response.authResponse.accessToken,
expiration_date: expDate
}
authService.facebookLogin(response.authResponse.accessToken).then(function() {
fbLogged.resolve(authData);
});
};
//This is the fail callback from the login method
function fbLoginError(error) {
var fbLogged = $q.defer();
fbLogged.reject(error);
alert(error);
$ionicLoading.hide();
};
//this method is to get the user profile info from the facebook api
function getFacebookProfileInfo() {
var info = $q.defer();
facebookConnectPlugin.api('/me', "",
function(response) {
info.resolve(response);
},
function(response) {
info.reject(response);
}
);
return info.promise;
}
//This method is executed when the user press the "Login with facebook" button
function fbLogin() {
if (!cordova) {
//this is for browser only
facebookConnectPlugin.browserInit(fbAppId);
}
//check if we have user's data stored
var user = UserService.getUser();
facebookConnectPlugin.getLoginStatus(function(success) {
//alert(JSON.stringify(success, null, 3));
if (success.status === 'connected') {
// the user is logged in and has authenticated your app, and response.authResponse supplies
// the user's ID, a valid access token, a signed request, and the time the access token
// and signed request each expire
facebookConnectPlugin.api("me/?fields=id,first_name,last_name,link,gender,email", ["public_profile", "email"],
function(result) {
//alert("Result: " + JSON.stringify(result));
//alert(result.first_name);
})
var accessToken = success.authResponse.accessToken;
authService.facebookLogin(accessToken).then(function() {
$state.go('app.map');
}, function(err) { alert('auth failed: ' + JSON.stringify(err, null, 2)); });
} else {
//if (success.status === 'not_authorized') the user is logged in to Facebook, but has not authenticated your app
//else The person is not logged into Facebook, so we're not sure if they are logged into this app or not.
$ionicLoading.show({
template: 'Loging in...'
});
// permissions from facebook
facebookConnectPlugin.login([
'email',
'public_profile',
'user_about_me',
'user_likes',
'user_location',
'read_stream',
'user_photos'
], fbLoginSuccess, fbLoginError);
fbLogged.promise.then(function(authData) {
var fb_uid = authData.id,
fb_access_token = authData.access_token;
//get user info from FB
getFacebookProfileInfo().then(function(data) {
var user = data;
user.picture = "http://graph.facebook.com/" + fb_uid + "/picture?type=large";
user.access_token = fb_access_token;
//save the user data
//store it on local storage but it should be save it on a database
UserService.setUser(user);
$ionicLoading.hide();
$state.go('app.map');
});
});
}
});
}
})();
I'm learning angular, and I'm trying to use a service to store data from an HTTP request, and be able to access it later.
Problem:
Data object is empty every time I try to retrieve it, which causes it to make a new call. I'm using this in the context of a ui-router resolve(does this cause the service to re-instantiate and lose the data)?
Service:
evaApp.factory('userService', ['$http', '$q', function ($http, $q) {
var user = {};
return {
makeRequest : function(url, uid) {
var deferred = $q.defer();
if (!uid) { uid = ''; };
$http.get(url, { params : { userId : uid } }).then(function (res) {
deferred.resolve(res.data);
});
return deferred.promise;
},
getUser : function(userId) {
console.log(user); // user is always empty
if(!user || !user._id) {
user = this.makeRequest('/api/user/get', userId);
};
return user;
}
}
}]);
Addition:
Data storage is working using PSL's solution. Data retrieval is not: Link to question.
this.makeRequest returns a promise and it does not have a _.id property which is causing it to make the ajax call again (due the condition if(!user || !user._id) {). just return the promise itself from getUser and use it. Remember you are not assigning the user instead assigning a promise by doing user = this.makeRequest('/api/user/get', userId);
Instead just do:-
var user = {};
getUser : function(userId) {
return user[userId] || (user[userId] = this.makeRequest('/api/user/get', userId)
.catch(function(){ user = null })); //nullify in case of error for retry
}
and in make request just do:
makeRequest : function(url, uid) {
if (!uid) { uid = ''; };
return $http.get(url, { params : { userId : uid } }).then(function (res) {
return res.data;
});
},
and while making call from controller you would do:-
mySvc.getUser(userId).then(function(user){
myCtrlInstance.user = user;
});
Note: Avoid using deferred anti-pattern when you already have an operation that returns a promise.
You can make something like this:
evaApp.factory('userService', ['$http', '$q', function ($http, $q) {
var user = {};
return {
makeRequest : function(url, uid) {
var deferred = $q.defer();
if (!uid) { uid = ''; };
$http.get(url, { params : { userId : uid } }).then(function (res) {
user = res.data;
deferred.resolve(user);
});
return deferred.promise;
},
getUser : function(userId) {
console.log(user); // user is always empty
if(!user || !user._id) {
return this.makeRequest('/api/user/get', userId);
};
var deferred = $q.defer();
deferred.resolve(user);
return deferred.promise;
}
}
}]);
And then get the user details like this (the 1 is just for the example):
userService.getUser(1).then(
function( data ) {
console.log(data);
}
);
I have a simple AngularJS app running in a Chrome Extension making use of the Storage API. Having an issue with the async nature of Storage; I've abstracted the storage away into a 'UserService' that sets and gets the data as a factory:
app.factory('UserService',
function($q, AppSettings) {
var defaults = {
api: {
token: AppSettings.environments[1].api.token
},
email: ''
};
var service = {
user: {},
save: function() {
chrome.storage.sync.set({'user': angular.toJson(service.user)});
},
restore: function() {
var deferred = $q.defer();
chrome.storage.sync.get('user', function(data) {
if(!data) {
chrome.storage.sync.set({'user': defaults});
service.user = defaults;
} else {
service.user = angular.fromJson(data.user);
}
deferred.resolve(service);
});
return deferred.promise;
}
};
// set the defaults
service.restore().then(function(data) {
console.log(data);
return data;
});
});
The console.log() call above dumps out the data as expected. However, when I am including the UserService in other factories (I have an APIService that makes use of a user-specific API token), the UserService parameter is being flagged as 'undefined' in the code below:
app.factory('APIService',
function($resource, $http, UserService, AppSettings) {
var token = UserService.user.api.token;
...
});
I am sure I am not fully grasping the Angular promise pattern in terms of consuming resolved promises throughout the app.
Updated code:
app.factory('UserService',
function($q, AppSettings) {
var defaults = {
api: {
token: AppSettings.environments[1].api.token
},
email: ''
};
var service = {
user: {},
save: function() {
chrome.storage.sync.set({'user': angular.toJson(service.user)});
},
restore: function() {
var deferred = $q.defer();
chrome.storage.sync.get('user', function(data) {
if(!data) {
chrome.storage.sync.set({'user': defaults});
service.user = defaults;
} else {
service.user = angular.fromJson(data.user);
}
deferred.resolve(service.user);
});
return deferred.promise;
}
};
// set the defaults
service.restore().then(function(data) {
console.log(data);
return data;
});
return service;
});
Edit/Additional Info:
Ok, getting close. Have refactored so that I am returning the object properly, but the issue now is that when the APIService gets created and tries to use the properties of the UserService object, they simply don't exist yet as they are only created after the async restore method is resolved. So it's not possible to access the UserService.user.api.token property, as it doesn't exist at that point, so the question is, how do I get that data in APIService when I need it if it is not available at that point? I'm trying to avoid having to put the entire contents of APIService into a callback that fires after a hypothetical new UserService.get() method that calls the callback on resolution of the promise. Any final guidance appreciated.
Your service is wrong. Please look at my fix:
app.factory('UserService',
function($q, AppSettings) {
var defaults = {
api: {
token: AppSettings.environments[1].api.token
},
email: ''
};
var service = {
user: {},
save: function() {
chrome.storage.sync.set({'user': angular.toJson(service.user)});
},
restore: function() {
var deferred = $q.defer();
chrome.storage.sync.get('user', function(data) {
if(!data) {
chrome.storage.sync.set({'user': defaults});
service.user = defaults;
} else {
service.user = angular.fromJson(data.user);
}
deferred.resolve(service.user); // <--- return the user in here
});
return deferred.promise;
}
};
// set the defaults
service.restore().then(function(data) {
console.log(data);
return data;
});
return service; // <--- return the service to be used injected when injected
});
[EDIT]
answer to your new question: Dont access user directly. create a new function in your service like getUser() that returns a promise. In that function return the user if it is already retreived otherwise return the restore() function:
var service = {
user: null,
getUser: function() {
if (service.user)
{
var deferred = $q.defer();
deferred.resolve(service.user);
return deferred.promise;
}
else
return service.restore();
},
save: function() {
chrome.storage.sync.set({'user': angular.toJson(service.user)});
},
restore: function() {
var deferred = $q.defer();
chrome.storage.sync.get('user', function(data) {
if(!data) {
chrome.storage.sync.set({'user': defaults});
service.user = defaults;
} else {
service.user = angular.fromJson(data.user);
}
deferred.resolve(service.user); // <--- return the user in here
});
return deferred.promise;
}
};
You're not returning an object from your factory. So when you try to inject your UserService parameter, it gives undefined because you haven't returned anything from your UserService function.
If you return your service variable, I think you'll get the behavior you're looking for.
Hi in the following Angular controller i try to initiate facebook login with Parse.com.
So I created a promise triggered on fbLogIn. What it is supposed to do, is first login to facebook, and grab first_name and store it in fieldValuesService.ff.
THEN, it is supposed to access this value and do something with it. For illustration purpose I just used console logs.
What happens is that the second console.log in second then is triggered before the first one from first .then thus is undefined.
I don't understand why anything in the second .then can be triggered before first one in this situation.
Also second problem, after a logout, the fbLogIn function is sometime inactive: it won't trigger the login process again.
If you have a clue on this issue your help will be greatly appreciated.
.controller('logController',
function ($scope, $q, fieldValuesService) {
var defer = $q.defer();
defer.promise
.then(function() {
Parse.FacebookUtils.logIn(null, {
success: function(user) {
if (!user.existed()) {
alert("User signed up and logged in through Facebook!");
} else {
$scope.currentUser = user;
$scope.$apply();
FB.api('/me', function(response) {
fieldValuesService.ff = response.first_name;
console.log(fieldValuesService.ff); //logs bob
});
}
},
error: function(user, error) {
alert("User cancelled the Facebook login or did not fully authorize.");
}
});
})
.then(function(){
console.log(fieldValuesService.ff); //logs undefined
});
$scope.fbLogIn = function() {
defer.resolve();
};
// Parse log out
$scope.logOut = function(form) {
Parse.User.logOut();
$scope.currentUser = null;
};
});
Maybe if you restructure your code, things will become a little bit easier.
I recommend to refactor everything FB related into its own service like:
module.factory('FBService', function ($q) {
var login,
logout,
getInformation;
login = function () {
var defered = $q.defer();
Parse.FacebookUtils.logIn(null, {
success: function (user) {
defered.resolve(user);
},
error: function (user, error) {
defered.reject(user, error);
}
});
return defered.promise;
};
logout = function () {
var defered = $q.defer();
Parse.User.logOut();
defered.resolve();
return defered.promise;
};
getInformation = function () {
var defered = $q.defer();
FB.api('/me', function (response) {
defered.resolve(response);
});
return defered.promise;
}
return {
login: login,
logout: logout,
getInformation: getInformation
};
});
module.controller("LoginCtrl", function ($scope, FBService, fieldValuesService) {
$scope.fbLogIn = function () {
FBService.login().then(function (user) {
$scope.currentUser = user;
return FBService.getInformation();
}).then(function (information) {
fieldValuesService.ff = information.first_name;
console.log(fieldValuesService.ff);
});
};
$scope.logOut = function () {
FBService.logout().then(function () {
$scope.currentUser = null;
});
};
});
This is my factory code. The callback is async so i put it under $rootScope.safeApply().
Then I call console.log(authService.authUser) in my controller but it still return undefined when user logged in. But it is find if user not login and will show 'not login' in console. Any idea?
myapp.factory('authService', ['$rootScope', function($rootScope) {
var auth = {};
$rootScope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
auth.firebaseAuthClient = new FirebaseAuthClient(FIREBASEREF, function(error, user) {
$rootScope.safeApply(function() {
if (user) {
auth.authUser = user;
//auth.isLoggedIn = true;
} else if (error) {
auth.authError = error;
} else {
auth.not = 'not login';
//auth.isLoggedIn = false;
}
});
});
auth.login = function() {
this.firebaseAuthClient.login('facebook');
};
auth.logout = function() {
this.firebaseAuthClient.logout();
};
return auth;
}]);
UPDATED
auth.callback = function(error, user) {
if (user) {
deferred.resolve(user);
} else if (error) {
deferred.reject(error);
} else {
//deferred.reject('not login'); // there is no callback value here
}
return deferred.promise;
}
in controller
callback().then(function(response) {
$scope.isLoggedIn = true;
}, function(response) {
$scope.isLoggedIn = false //How can i set false here?
});
UPDATE 2
Now every thing work fine, I'm able to monitoring user login state. But still having a problem. Check the code below
authService.callback().then(function(success){
$rootScope.isLoggedIn = true; //If promise return success set isLoggedIn true
}, function(fail){
**//If user not login set isLoggedIn false;
//I have problem here because i'm not able to deferred.reject below**
$rootScope.isLoggedIn = false;
})
auth.callback = function(error, user) {
$timeout(function() {
if (user) {
deferred.resolve(user);
} else if (error) {
deferred.reject(error);
} else {
//If this line is added,
//.then() will not return anything not even undefined with no error,
//No mater user logged-in or not login.
//If I comment it out, everything will work fine but how can I
//set isLoggedIn = false?
deferred.reject();
}
}, 0);
return deferred.promise;
}
Wrap the outside service's deferred resolve in a $timeout block to let angular know when its resolved. This way when your controller runs then callback, it'll be in a $digest cycle.
See this fiddle as a working proof of concept: http://jsfiddle.net/Zmetser/rkJKt/
// in controller
authService.login().then(success, error);
// service
myapp.factory('authService', ['$q', '$timeout', function( $q, $timeout ) {
var auth = {},
deferred;
firebaseAuthClient = new FirebaseAuthClient(FIREBASEREF, afterAuth);
function afterAuth( error, user ) {
// Let angular know the deferred has been resolved.
$timeout(function () {
if (user) {
deferred.resolve(user);
} else if (error) {
deferred.reject(error);
} else {
deferred.reject(); // there is no callback value here
}
}, 0);
}
auth.login = function() {
deferred = $q.defer();
firebaseAuthClient.login('facebook');
return deferred.promise;
};
auth.logout = function() {
deferred = $q.defer();
firebaseAuthClient.logout();
return deferred.promise;
};
return auth;
}]);