I'm doing this login exercise where users can login and post notes, and view the notes that they've posted. My problem is when I logout and login with a different user I see the notes from the previous user.
Here's an illustration:
I log in with a different user then this shows up:
I restart the page and the appropriate note shows up:
The controller for this:
exports.homeController = function ($scope, $location, $q, $users, $window, $notes, $http) {
var auth = function () {
var userInfo = $users.getUserInfo()
if (userInfo) {
return $q.when(userInfo)
} else {
return $q.reject({ authenticated: false })
}
}
$scope.userInfo = auth()
myNotes($scope.userInfo.$$state.value.accessToken) // I invoke my function to get the notes for each specific user but it doesn't seem to work.
$scope.logout = function () {
$users.logout()
.then(function (results) {
$scope.userInfo = null
$scope.myNotes = null
$location.path('/')
}, function (err) {
console.log(err)
})
}
$scope.notes = {
notes: ''
}
$scope.postNote = function () {
$notes.postNotes($scope.userInfo.$$state.value.accessToken, $scope.notes)
.then(function (result) {
$scope.myNotes.push($scope.notes)
$scope.notes = ''
}, function (err) {
console.log(err)
})
}
function myNotes (user_id) {
$notes.getMyNotes(user_id)
.then(function (result) {
console.log(result)
$scope.myNotes = result.data
}, function (err) {
console.log(err)
})
}
}
This is the app https://login-sys.herokuapp.com/
I've found your non-minified code for the services.
Based on that I think the problem is that you declare var deferred = $q.defer() one time in the $notes service.
I think it should be "renewed" every time the service methods are called:
function getMyNotes (user_id) {
var deferred = $q.defer();
$http.get('/api/myNotes/' + user_id + '?access_token=' + user_id)
.then(function (result) {
deferred.resolve(result)
}, function (err) {
deferred.reject(err)
});
return deferred.promise
}
Similarly in postNotes.
The second time you return the same promise with the same value, so your homeController's getMyNotes function will get the same result despite the $notes service making a new request.
In the $users service's logout and signup functions you are already using it correctly.
Related
I am trying to pass the data between controllers when each http request is successful. that said, here is a hierarchy
<div ng-controller="ParentCtrl as parent">
<div ng-controller="ChildOneCtrl as chi1"></div>
<div ng-controller="ChildTwoCtrl as chi2"></div>
<div ng-controller="ChildThreeCtrl as chi3"></div>
</div>
loading data within each next controller depends on previous one, namely when http request is successful.
ChildOneCtrl:
function ChildOneCtrl ($scope, sharedService) {
var chi1 = this;
sharedService.getChildOneData()
.then(function (res) {
$rootScope.$emit('childOneDataEmited', res.data);
}).catch(function (err) {
console.log(err);
});
}
ChildTwoCtrl:
function ChildTwoCtrl ($scope, $rootScope, sharedService) {
var chi2 = this;
var onEmitChildOne = $rootScope.$on('childOneDataEmited', function (event, data) {
getData(data);
});
$scope.$on('$destroy', onEmitChildOne);
function getData(data){
var chi1Data = data;
if(chi1Data.uEnabled){
sharedService.getChildTwoData()
.then(function (res) {
$rootScope.$emit('childTwoDataEmited', res.data);
}).catch(function (err) {
console.log(err);
});
}
}
}
ChildThreeCtrl:
function ChildThreeCtrl ($scope, $rootScope, sharedService) {
var chi3 = this;
var onEmitChildThree = $rootScope.$on('childTwoDataEmited', function (event, data) {
getData(data);
});
$scope.$on('$destroy', onEmitChildThree);
function getData(data){
var chi2Data = data;
sharedService.getChildThreeData()
.then(function (res) {
//to do some data manipulation
console.log(res)
console.log(chi2Data)
}).catch(function (err) {
console.log(err);
});
}
}
while this get the job done, the hierarchy is likely going to change, become deeper so I am wondering if there is a better way to do it so I dont overuse events ?
Why don't you share the data it self through the sharedService ?
After you get the data from first controller, just assign it to a shared variable in sharedService and in the child controller (second one) just set a watch for this shared variable. like this:
function ChildOneCtrl ($scope, sharedService) {
var chi1 = this;
sharedService.getChildOneData()
.then(function (res) {
sharedService.sharedData = res.Data;
}).catch(function (err) {
console.log(err);
});
}
function ChildTwoCtrl ($scope, $rootScope, sharedService) {
var chi2 = this;
$scope.watch('sharedService.sharedData', function(newValue) {
// do something like calling another endpoint using http.
});
}
I didn't try it, it may fail, but the idea is to share data throug service.
UPDATE
Another approach is to have two then() or more:
function sharedService($q, $http) {
var service = {
sharedData1Promise: { then: function() {} },
sharedData2Promise: { then: function() {} },
sharedData3Promise: { then: function() {} },
getSomeData1: getSomeData1
};
function getSomeData1() {
sharedData1Promise = $http.get( /* what ever is here */ );
return sharedData1Promise;
}
function getSomeData2() {
sharedData2Promise = $http.get( /* what ever is here */ );
return sharedData2Promise;
}
function getSomeData3() {
sharedData3Promise = $http.get( /* what ever is here */ );
return sharedData3Promise;
}
return service;
}
function ChildOneCtrl ($scope, sharedService) {
var chi1 = this;
sharedService.getSomeData1()
.then(function (res) {
/* do something */
}).catch(function (err) {
console.log(err);
});
}
function ChildTwoCtrl ($scope, sharedService) {
var chi2 = this;
sharedService.sharedData1Promise
.then(function (res) {
sharedService.getSomeData2();
/* do something with data coming from the first child calling getSomeData1 method */
}).catch(function (err) {
console.log(err);
});
}
I have created login page and for accessing data I have used JSON File. when I click on the login() btn , userdata should match with the server data. But I am getting error and I am not able to send the $http response to the client side from server. If the rows with that name and password matches it return true, if not return false. then the client should parse this response.
But I don't know how What I am doing wrong?
Any help / advice would be greatly appreciated.
AngualJS:
/* server-side */
app.factory('auth', function ($http, session) {
var authService = {};
authService.login = function (username, password) {
var response;
$http.post('http://localhost:3000/loginfo')
.then(
function successCallback(response) {
var user = response.data;
console.log(user);
if (user.username == username && user.password == password) {
var signinfo = response.data;
console.log(response.data);
} else {
console.log('Error');
}
})
};
authService.isAuthenticated = function () {
return {
isAuthenticated: false,
user: null
}
};
return authService;
});
/* client-side */
app.controller('credientials', function ($scope, $http, auth) {
$scope.userCred = {
username: '',
password: ''
};
/*-----Form Submition-----*/
$scope.log = function (userCred) {
auth.isAuthenticated = true;
auth.login(userCred.username, userCred.password, function (response) {
if (response.success) {
console.log('success');
} else {
console.log('Error');
}
})
};
});
function get (id, ignore) {
var deferred = $q.defer();
$http.get('v1/info/' + id, {
ignoreAuthModule: ignore
})
.success(function (data) {
deferred.resolve(data.data);
})
.error(function (reason) {
deferred.reject(reason.message););
});
return deferred.promise;
}
init();
function init(){
users.get($routeParams.id)
.then(function (data) {
if(data.has_something === 1){
$scope.hasSomething = true;
}else{
$scope.hasSomething = false;
}
});
}
I have a Service that get the information about user using promise and Fetching information from the service with init function
//if i call init function this should call two times one from function initialization and other i'm calling it from service
how can i stop two times calling api I mean it should call one time if already called
You're using the explicit promise creation antipattern here, and your code could be much simpler. Here is how you can use memoization to avoid requesting the same user twice:
.factory('users', ['$http', function ($http) {
var userPromises = {};
function get (id, ignore) {
if (!userPromises[id]) {
userPromises[id] = $http.get('v1/info/' + id, {
ignoreAuthModule: ignore
})
.then(function (data) {
return data.data;
})
.catch(function (reason) {
throw new Error(reason.message);
});
}
return userPromises[id];
}
return {
get: get
};
});
You can assign your deferred.promise to some variable and then return that variable, and before your http call just check whether that variable is already defined or not
function get (id, ignore) {
if (angular.isUndefined(user)) {
var deferred = $q.defer();
$http.get('v1/info/' + id, {
ignoreAuthModule: ignore
}).then(function(response) {
if (response.status == 200) {
deferred.resolve(response.data);
} else {
deferred.reject(response.data);
};
user = deferred.promise;
return user;
} else {
return user;
}
}
This way your api will get called only once.
I am new to angularjs.I am using factories where i have written the fb login code.
And during the last step i am sending all the data to my server where the user is registered in my database and the token is sent.
Here is the code.
'use strict'
APP.factory('authenticationFactory',['ENV','$http','$rootScope', function (ENV,$http,$rootScope) {
return {
socialLogin:function(data){
return $http.post($rootScope.apiURL+'sociallogin',data).then(function (resp) {
if(resp.status == 200) {
return resp.data;
}
})
},
fbLogin: function () {
var FB = window.FB;
var scopes = 'public_profile,email';
var that = this;
FB.login(function (response) {
return that.facebookStatusChangeCallback(response);
}, {scope: scopes});
},
facebookStatusChangeCallback: function(response){
if (response.status === 'connected') {
// Logged into your app and Facebook.
var r = this.facebookApiRequest(response);
console.log(r);
} else if (response.status === 'not_authorized') {
// The person is logged into Facebook, but not your app.
console.log('Please log into this app.');
} else {
// The person is not logged into Facebook, so we're not sure if
// they are logged into this app or not.
console.log('Please log into Facebook.');
}
},
facebookApiRequest: function (authResponse) {
var that = this;
var r = FB.api('/me?fields=id,name,email,gender,first_name,last_name,age_range,link,birthday', function (response) {
var r = FB.api("/" + response.id + "/picture?height=720", function (pictureResponse) {
if (pictureResponse && !pictureResponse.error) {
/* handle the result */
response.profile_pic = pictureResponse.data.url;
response.access_token = authResponse.authResponse.accessToken;
response.provider = 'facebook';
response.devicetoken = '';
response.full_name = response.first_name+' '+response.last_name;
var r = that.socialPluginLogin(response).then(function (resp) {
return that.resp;
});
return r;
} else {
console.log('error while fatching fb pic');
}
});
console.log(r);
});
console.log(that);
},
socialPluginLogin : function (data) {
var resp = this.socialLogin(data).then(function (resp) {
return resp;
});
return resp;
}
};
}]);
I am calling the fbLogin() function from my controller. i need the response from the function socialLogin() so that i can change the state.
Where am i going wrong.??
The answer was pointing in the wrong direction, another try:
Your function fbLogin should return a promise, which can be resolved by socialLogin later. Since fbLogin doesn't return a thing, you don't receive any signal from the completed login.
See this:
// We add $q here
APP.factory('authenticationFactory',['ENV','$http','$rootScope','$q', function (ENV,$http,$rootScope,$q) {
var loginPromise;
return {
socialLogin:function(data){
return $http.post($rootScope.apiURL+'sociallogin',data).then(function (resp) {
if(resp.status == 200) {
// This is your connection to the controller
loginPromise.resolve(resp.data);
return resp.data;
}
})
},
fbLogin: function () {
var FB = window.FB;
var scopes = 'public_profile,email';
var that = this;
FB.login(function (response) {
return that.facebookStatusChangeCallback(response);
}, {scope: scopes});
// Create and return a promise
loginPromise = $q.defer();
// EDIT: My fault, return the promise:
return loginPromise.promise;
},
//...
And add this to the controller:
authenticationFactory.fbLogin().then(function(data){
// Check it out:
console.dir(data);
})
Additional things you should consider:
Define your functions in the function body, not in the return statement. You can eliminate that=this this way
Only return the API, not all the functions
Read up on promises, they are the way to go in the angular world. You might as well use callbacks, but those are tedious to handle.
Change your socialLogin function to below, your function would return a promise object which you can consume in socialPluginLogin via then which you are already doing.
socialLogin:function(data){
return $http.post($rootScope.apiURL+'sociallogin',data)
},
My friend and I are building an app - my friend is on the backend (Node.js) and I'm on the front.
He implemented sessions on his end and provided me with the URL I need to call to log in. For example, a POST request
http://ourapp.heroku.com/login
with which username and password are passed.
On my side, in the Angular app, I create a login page which calls an Angular service when Login is clicked. If this service receives a 200 from the server, it does:
$cookieStore.put(cookieNames.LOGGED_IN_COOKIE, true);
$state.go('home', {}, {reload: true});
The problem is that we're having weird issues with the app on the front end. For example logging in and out often don't work. Also, users are able to go to pages even after they log out. I figured out (at least I think) that I'm not properly storing the Cookie I receive from the server, I'm only storing my own.
This whole Angular thing is still weird to me, because in PHP or Python apps you get a page request from the client and verify if he's logged in before sending him the page he requested. In Angular it's different - the user has all of the pages already. So how do I limit what he can see without logging in and how to I properly keep track of the server's cookie?
If you use ui-router, you can do something similar to this:
First introduce some kind of access-levels to your states
$stateProvider
.state('admin', {
url: "/admin",
templateUrl: "/app/views/admin.html",
controller: "AdminController",
data: {
accessLevel: 'admin'
}
})
then you have to check on state change, if your logged in user has the required access-level:
You can create an auth service which implements your logic to log your user in, as example you can use this service
angular.module('app')
.factory("AuthService", ["$rootScope", "$http", "AuthSession", "AuthHttpBuffer", "AUTH_EVENTS", function ($rootScope, $http, AuthSession, AuthHttpBuffer, AUTH_EVENTS) {
function loginFailed() {
$rootScope.$broadcast("auth-change", AUTH_EVENTS.loginFailed);
};
AuthSession.load();
$rootScope.$on('$stateChangeStart', function (event, nextState) {
if (nextState.data && nextState.data.accessLevel && !service.isAuthorized(nextState.data.accessLevel)) {
event.preventDefault();
$rootScope.$broadcast('auth-change', AUTH_EVENTS.loginRequired, nextState.name);
}
});
var service = {
login: function (credentials) {
return $http
.post('/api/account/login', credentials)
.success(function (data, status) {
if ((status < 200 || status >= 300) && data.length >= 1) {
loginFailed();
return;
}
AuthSession.create(data.AccessToken, data.User);
$rootScope.$broadcast("auth-change", AUTH_EVENTS.loginSuccess);
AuthHttpBuffer.retryAll();
}).error(function (data, status) {
loginFailed();
});
},
cancel: function () {
AuthHttpBuffer.rejectAll();
},
logout: function () {
AuthSession.destroy();
$rootScope.$broadcast("auth-change", AUTH_EVENTS.logoutSuccess);
},
isAuthenticated: function () {
return (AuthSession.token !== null);
},
isAuthorized: function (accessLevel) {
if (!accessLevel) return true;
return (this.isAuthenticated() && AuthSession.user.UserRoles.indexOf(accessLevel) !== -1);
}
}
return service;
}]);
and your AuthSession service:
angular.module('app')
.factory("AuthSession", ["$rootScope", "$window", "AUTH_EVENTS", function ($rootScope, $window, AUTH_EVENTS) {
var sessionService = {
user: null,
token: null,
//load the stored session data
load: function () {
var user = ...yourdata... //TODO implement load user data;
var token = ...yourdata... //implement load user data;
if (!user || !token) return;
if (!this.checkTokenExpiration(token)) return;
this.user = user;
this.token = token;
$rootScope.$broadcast("auth-change", AUTH_EVENTS.loginSuccess);
},
//save the current data to the session storage
save: function () {
//TODO save your userdata/token etc.
},
//create the current user with the assosiated token
create: function (token, user) {
this.token = token;
this.user = user;
if (!angular.isArray(this.user.UserRoles))
this.user.UserRoles = [this.user.UserRoles];
this.save();
},
//destroy an user with all assosiated data
destroy: function () {
this.token = null;
this.user = null;
//TODO clear your saved data here
},
//check if the supplied access token data is expired
checkTokenExpiration: function (token) {
if (token === undefined || token === null) return false;
var retval = (new Date(token.TokenExpires).getTime() > new Date().getTime());
if (retval === false) {
sessionService.destroy();
$rootScope.$broadcast("auth-change", AUTH_EVENTS.sessionTimeout);
}
return retval;
}
}
return sessionService;
}]);
and the constants:
angular.module('app')
.constant('AUTH_EVENTS', {
loginSuccess: 'auth-login-success',
loginFailed: 'auth-login-failed',
logoutSuccess: 'auth-logout-success',
loginRequired: 'auth-login-required',
sessionTimeout: 'auth-session-timeout',
notAuthorized: 'auth-not-authorized'
});
If you want be able to catch urls, where you haven't the right accesrights, you can send the request to a http buffer:
angular.module('app')
.factory('AuthHttpBuffer', ["$injector", function ($injector) {
/** Holds all the requests, so they can be re-requested in future. */
var buffer = [];
/** Service initialized later because of circular dependency problem. */
var $http;
function retryHttpRequest(config, deferred) {
function successCallback(response) {
deferred.resolve(response);
}
function errorCallback(response) {
deferred.reject(response);
}
$http = $http || $injector.get('$http');
$http(config).then(successCallback, errorCallback);
}
return {
/**
* Appends HTTP request configuration object with deferred response attached to buffer.
*/
append: function (config, deferred) {
buffer.push({
config: config,
deferred: deferred
});
},
/**
* Abandon or reject (if reason provided) all the buffered requests.
*/
rejectAll: function (reason) {
if (reason) {
for (var i = 0; i < buffer.length; ++i) {
buffer[i].deferred.reject(reason);
}
}
buffer = [];
},
/**
* Retries all the buffered requests clears the buffer.
*/
retryAll: function () {
for (var i = 0; i < buffer.length; ++i) {
retryHttpRequest(buffer[i].config, buffer[i].deferred);
}
buffer = [];
}
};
}]);
and if you haven't enough you can also add an interceptor, that triggers an auth change event, if the server response is unauthorized:
angular.module('app')
.factory('AuthInterceptor', ["$rootScope", "$q", "AuthSession", "AuthHttpBuffer", "AUTH_EVENTS", function ($rootScope, $q, AuthSession, AuthHttpBuffer, AUTH_EVENTS) {
return {
request: function (config) {
config.headers = config.headers || {};
if (AuthSession.token) {
config.headers.Authorization = 'Bearer ' + AuthSession.token.TokenKey;
}
return config;
},
responseError: function (rejection) {
if (rejection.status === 401) {
var deferred = $q.defer();
AuthHttpBuffer.append(rejection.config, deferred);
if (AuthSession.token) {
$rootScope.$broadcast('auth-change', AUTH_EVENTS.notAuthorized);
} else {
$rootScope.$broadcast('auth-change', AUTH_EVENTS.loginRequired);
}
return deferred.promise;
}
return $q.reject(rejection);
}
}
}]);
this interceptor also adds a session token to all requests if available.
to use this interceptor, you have to add the following two lines to your app.config():
$httpProvider.defaults.withCredentials = true;
$httpProvider.interceptors.push("AuthInterceptor");