Angular service unable to execute function - javascript

function loadUserCredentials() {
var token = new Object();
token.token = window.localStorage.getItem(LOCAL_TOKEN_KEY);
$http.post("http://localhost:8080/checkTokenValid",token).then(function(result){
console.log(result.data.success);
if(result.data.success){
useCredentials();
}
});
};
This loadCredentials() is loaded everytime the page refresh, I'm checking if it is authenticated at my angular.run
controlApp.run(function ($location, $rootScope,controlProvider){
$rootScope.$on('$routeChangeStart',function(event,next,nextParems){
if (!controlProvider.isAuthenticated()){
$location.path('/login');
}
})
});
I think this is running before loadCredentials complete, so I always get login page. Is there any way I can delay the angular.run? So that I can have my loadCredentials to check is the user having the valid token.

Yes you can do that by $timeout :
controlApp.run(function ($location, $rootScope, controlProvider, $timeout) {
$rootScope.$on('$routeChangeStart', function (event, next, nextParems) {
$timeout(function () {
if (!controlProvider.isAuthenticated()) {
$location.path('/login');
}
}, 500);
})
});

Related

How to avoid an AJAX request before getting response for previous call [duplicate]

In angularJS, With one call, when get the service response need access that json value in multiple controllers but in same page
I have two controller js file and both controllers are called in the same page when I called the service "this.getNavigationMenuDetails" in the first controller.js and as well as called in the controller2.js file as well. without timeout function, I want to access that same response which I get it from the "this.getNavigationMenuDetails" service in controller2.js. But it happened that service call twice in the page. I don't want to call the same service twice in a page.
When js are loading that time both controllers are called in the same layer then getting the response from the service so on the second controller2.js file code is not execute after the response. How can I solve this issue so that only one call i can get the response and access this response in controller2.js also.
controler1.js
var app = angular.module("navApp", []);
app.controller("navCtrl", ['$scope', 'topNavService', '$window', function ($scope, $timeout, topNavService, $window) {
$scope.menuItemInfo = {};
/*Navigation Menu new Code */
$scope.getNavigationDetails = function () {
topNavService.getNavigationMenuDetails().then(function (result) {
$scope.menuItemInfo = result;
angular.forEach($scope.menuItemInfo.items, function (val, key) {
if (val.menuTitle ===
$window.sessionStorage.getItem('selectedNavMenu')) {
if ($scope.menuItemInfo.items[key].isEnabled) {
$scope.menuItemInfo.items[key].isActive = 'highlighted';
} else {
$window.sessionStorage.removeItem('selectedNavMenu');
}
}
if (val.menuTitle === 'Find a Fair' && !val.hasSubMenu) {
$scope.menuItemInfo.items[key].redirectTo = appConfig.findafairpageurl;
}
});
});
};
$scope.init = function () {
if ($window.location.pathname.indexOf('all-my-fairs.html') > 0) {
if (angular.isDefined($cookies.get('cpt_bookfair'))) {
$cookies.remove('cpt_bookfair', {
path: '/'
});
}
}
$scope.getNavigationDetails();
$scope.callOnLoad();
};
$scope.init();
}]);
app.service('topNavService', ['$http', '$timeout', '$q'function ($http, $timeout, $q) {
var menuInfo;
this.getNavigationMenuDetails = function () {
if (!menuInfo) {
// If menu is undefined or null populate it from the backend
return $http.get("/etc/designs/scholastic/bookfairs/jcr:content/page/header-ipar/header/c-bar.getMenuDetails.html?id=" + Math.random()).then(function (response) {
menuInfo = response.data;
return menuInfo;
});
} else {
// Otherwise return the cached version
return $q.when(menuInfo);
}
}
}]);
Controller2.js
var app = angular.module('bookResourcePage', []);
app.controller('bookResourceCtrl', ['topNavService', '$scope', function (topNavService, $scope) {
$scope.topInfo = '';
topNavService.getNavigationMenuDetails.then(function success() {
$scope.productId = $scope.topInfo.isLoggedin;
$scope.linkParam = '?productId=' + $scope.productId;
}, function failure() {
console.error("something bad happened");
});
}]);
The service would work better if it cached the HTTP promise instead of the value:
app.service('topNavService', function ($http) {
var menuInfoPromise;
this.getNavigationMenuDetails = function () {
if (!menuInfoPromise) {
// If menu is undefined or null populate it from the backend
menuInfoPromise = $http.get(url);
};
return menuInfoPromise;
};
});
The erroneous approach of caching the value introduces a race condition. If the second controller calls before the data arrives from the server, a service sends a second XHR for the data.
You can do this with following approach.
Service.js
app.service('topNavService', function($http) {
var menuInfoPromise;
var observerList = [];
var inProgress = false;
this.addObserver(callback) {
if (callback) observerList.push(callback);
}
this.notifyObserver() {
observerList.forEach(callback) {
callback();
}
}
this.getNavigationMenuDetails = function() {
if (!menuInfoPromise && !inProgress) {
inProgress = true;
// If menu is undefined or null populate it from the backend
menuInfoPromise = $http.get(url);
this.notifyObserver();
};
return menuInfoPromise;
};
});
You have to make a function in service to add your controller's function on list. then each controller will register their get function on service and call service method to get data. first call will make service variable inProgress to true. so it will prevent for multiple server request. then when data available to service then it will call its notifyObserver function to message for all controller by calling their function.
Controller 1
app.controller('ctrl1', ['service', '$scope', function(service, $scope) {
service.addObserver('getData1'); //name of your controller function
$scope.getData1() {
service.getNavigationMenuDetails.then(function success() {
$scope.productId = $scope.topInfo.isLoggedin;
$scope.linkParam = '?productId=' + $scope.productId;
}, function failure() {
console.error("something bad happened");
});
}
$scope.getData1()
}]);
Controller 2
app.controller('ctrl1', ['service', '$scope', function(service, $scope) {
service.addObserver('getData2'); //name of your controller function
$scope.getData2() {
service.getNavigationMenuDetails.then(function success() {
$scope.productId = $scope.topInfo.isLoggedin;
$scope.linkParam = '?productId=' + $scope.productId;
}, function failure() {
console.error("something bad happened");
});
}
$scope.getData2()
}]);
with this approach you can real time update data to different controllers without have multiple same request to server.

Use local storage to store AngularJS data

I am currently using $rootScope to store user information and whether or not the user is logged in. I have tried using $window.localStorage, but with no success. My goal is to have items in my navbar appear through an ng-show once a user is logged on, have their username appear in the navbar, individual user profile view, all users view, etc. I need a persistent login. I have the navbar working with $rootscope, but whenever I try and transition over to $window.localStorage, it fails. Here is the code using $rootScope:
mainModule
angular.module('mainModule', [
'ui.router',
...
])
.config(configFunction)
.run(['$rootScope', '$state', 'Auth', function($rootScope, $state, Auth) {
$rootScope.$on('$stateChangeStart', function(event, next) {
if (next.requireAuth && !Auth.getAuthStatus()) {
console.log('DENY');
event.preventDefault();
$state.go('login');
} else if (Auth.getAuthStatus() || !Auth.getAuthStatus()) {
console.log('ALLOW');
}
});
}]);
Auth Factory
angular.module('authModule').factory('Auth', ['$http', '$state', function authFactory($http, $state) {
var factory = {};
var loggedIn = false;
var userData = {};
factory.getAuthStatus = function() {
$http.get('/api/v1/auth')
.success(function(data) {
if (data.status == true) {
loggedIn = true;
} else {
loggedIn = false;
}
})
.error(function(error) {
console.log(error);
loggedIn = false;
});
return loggedIn;
}
return factory;
}]);
Login Controller
function SigninController($scope, $rootScope, $http, $state) {
$scope.userData = {};
$scope.loginUser = function() {
$http.post('api/v1/login', $scope.userData)
.success((data) => {
$scope.userData = data.data;
$rootScope.loggedIn = true;
$rootScope.userData = data;
$state.go('home');
})
.error((error) => {
console.log('Error: ' + error);
});
};
}
Nav Controller
function NavbarController($scope, Auth) {
$scope.loggedIn = Auth.getAuthStatus();
}
EDIT EDIT EDIT
Here is how I am using local storage. These are the only things that changed.
Login Controller
function SigninController($scope, $window, $http, $state) {
$scope.userData = {};
$scope.loginUser = function() {
$http.post('api/v1/login', $scope.userData)
.success((data) => {
$scope.userData = data.data;
$window.localStorage.setItem('userData', angular.toJson(data));
$window.localStorage.setItem('loggedIn', true);
$state.go('home');
})
.error((error) => {
console.log('Error: ' + error);
});
};
}
Auth Factory
angular
.module('authModule')
.factory('Auth', ['$http', '$window', '$state', function authFactory($http, $window, $state) {
var factory = {};
factory.getAuthStatus = function() {
$http.get('/api/v1/auth')
.success(function(data) {
if (data.status == true) {
$window.localStorage.setItem('loggedIn', true);
} else {
$window.localStorage.setItem('loggedIn', false);
}
})
.error(function(error) {
console.log(error);
$window.localStorage.setItem('loggedIn', false);
});
return $window.localStorage.getItem('loggedIn');
}
return factory;
}]);
I see a potential problem with your use of localStorage.getItem('loggedIn').
Because localStorage only stores strings, what you get back is actually a stringified version of the boolean that you put in. If the string 'false' gets returned, your check of !Auth.getAuthStatus() in main module for example will always evaluate to boolean false because any non-empty string in JavaScript is "truthy".
i.e. !'false' === false (the same as !true === false)
You can get over this by using JSON.parse on the value in localStorage. e.g. JSON.parse(localStorage.getItem('loggedIn')) would parse the string 'false' to the Boolean false.
Simply replace $window.localStorage with window.localStorage and you should be fine.
For example:
function SigninController($scope, $window, $http, $state) {
$scope.userData = {};
$scope.loginUser = function() {
$http.post('api/v1/login', $scope.userData)
.success((data) => {
$scope.userData = data.data;
window.localStorage.setItem('userData', angular.toJson(data));
window.localStorage.setItem('loggedIn', true);
$state.go('home');
})
.error((error) => {
console.log('Error: ' + error);
});
};
}
This being said, storing authenticated status in localStorage (or sessionStorage) is not a good path to go down. Both key/value pairs can be read in the developer pane and then altered (aka spoofed) via the console. A better solution is to return a unique value (GUID) after a successful login and store it in a cookie (set to expire in a short amount of time, like 20 minutes) that can be read on the server and verified there. You can and should use $cookie for this. Your user login state should be controlled server-side, never client-side. The client should always have to prove that it is authenticated.
To persist login, create a service that handles your visitor and let that service handle the login/logout and provide the proof of being logged in. That proof of being logged in should always be a private value that is held internally by the service and not accessible outside of it.
(function () {
'use strict';
var visitorModelService = ['$http', function ($http) {
var loggedIn = false,
visitorModel = {
login:function(){
//do login stuff with $http here
//set loggedIn to true upon success
},
loggedIn:function(){
return loggedIn;
},
logout:function(){
//do logout stuff with $http here
//no matter what, set loggedIn to false
}
};
return visitorModel;
}];
var module = angular.module('models.VisitorModel', []);
module.factory('VisitorModel', visitorModelService);
}());
Doing this, you can simply check for visitor.loggedIn in your ng-show and have everything centralized. Such as:
<a ng-click='visitor.logout' ng-show='visitor.loggedIn'>Log Out</a>
Better yet, put the elements that are only visible to authenticated users in a div tag and hide/show them en-mass.

Meteor collection in ui-router resolve

I'm trying to use meteor angular js ui-router resolve to load information of one user selected from user list.
$stateProvider
.state('userprofile', {
url: '/user/:userId',
cache: false,
template: '<user-profile userinfo="$resolve.userinfo"></user-profile>',
controller: UserProfile,
controllerAs: name,
resolve: {
userinfo: function($stateParams) {
viewedUser = Meteor.users.findOne({
_id: $stateParams.userId
});
return viewedUser;
},
}
});
The problem is that, for the first time after from user list, user profile display correctly. However, page reload makes the userinfo becomes undefined.
I guest that from second time, the controller loaded already so that it display before resolve done?!
After a while searching, I tried $q and $timeout
resolve: {
userinfo: function($stateParams, $q, $timeout) {
deferred = $q.defer();
$timeout(function() {
deferred.resolve(Meteor.users.findOne({
_id: $stateParams.userId
}));
}, 1000);
return deferred.promise;
},
}
It works as I expected, user profile displayed every time I refresh the page.
But if I lower the delay to 500, it back to undefined when refreshed.
I not sure why in this case, longer delay works?
Thank you!
Here is the code that I use,
resolve: {
currentUser: ($q) => {
var deferred = $q.defer();
Meteor.autorun(function () {
if (!Meteor.loggingIn()) {
if (Meteor.user() == null) {
deferred.reject('AUTH_REQUIRED');
} else {
deferred.resolve(Meteor.user());
}
}
});
return deferred.promise;
}
}
This is from a tutorial by #urigo somewhere, which took me some time to find, but it works like a charm.
This code is handy to trap the case where authentication is required - put it at the top level in a .run method
function run($rootScope, $state) {
'ngInject';
$rootScope.$on('$stateChangeError',
(event, toState, toParams, fromState, fromParams, error) => {
console.log("$stateChangeError: "+error);
if (error === 'AUTH_REQUIRED') {
$state.go('login');
}
}
);
}
You can try this routes in resolve
if you use angular-meteor
resolve: {
'loginRequired': function ($meteor, $state) {
return $meteor.requireUser().then(function (user) {
if (user._id) {return true;}
}).catch(function () {
$state.go('login');
return false;
});
}
}

AngularJS: Promise in app.run() stuck in loop forever

I'm using app.run() in my AngularJS app to check whether a user is logged in before displaying the site to block access to various sites for non-registered users. I tried doing it with a promise because before, whenever I reloaded the page the isLoggedIn function would return false the getStatus hasn't returned the answer from the server yet.
Now using the promise, the site just calls itself in a loop forever, I guess because the process just repeats itself when the promise is resolved. Where am I going wrong and how could I fix this? Thanks in advance, help is much appreciated!
This is my code in app.js:
app.run(function($rootScope, $state, authService){
$rootScope.$on('$stateChangeStart', function(event, next, nextParams, from, fromParams){
event.preventDefault();
authService.getUserStatus().then(function(){
console.log(authService.isLoggedIn());
if(next.access.restricted && !authService.isLoggedIn()){
$state.go('index', {}, { reload: true });
} else {
$state.go(next, {}, { reload: true });
}
});
});
});
Here's the service authService.js:
(function(){
var app = angular.module('labelcms');
app.factory('authService', ['$q', '$timeout', '$http', function($q, $timeout, $http){
var user = null;
var isLoggedIn = function(){
if(user){
return true;
} else {
return false;
}
};
var getUserStatus = function(){
var deferred = $q.defer();
$http.get('/api/user/status')
.success(function(data){
if(data.status){
user = data.status;
deferred.resolve();
} else {
user = false;
deferred.resolve();
}
})
.error(function(data){
console.log('Error: ' + data);
user = false;
deferred.resolve();
});
return deferred.promise;
};
return ({
isLoggedIn: isLoggedIn,
getUserStatus: getUserStatus,
login: login,
logout: logout,
signup: signup
});
}]);
})();
It loops because every time you execute $state.go(next, {}, { reload: true }); it will hit your $rootScope.$on again.
I would check if we actually are on restricted route before you go into your security service.
app.run(function($rootScope, $state, authService){
$rootScope.$on('$stateChangeStart', function(event, next, nextParams, from, fromParams){
if(!next.access.restricted) return;
authService.getUserStatus().then(function(){
console.log(authService.isLoggedIn());
if(!authService.isLoggedIn()){
$state.go('index', {}, { reload: true });
});
});
});

Wait for response in AngularJS $http

Okay, so I'm pretty sure I've found the answer multiple times - I just don't understand it.
I'm building a single page application which on each $routeChangeStart checks if the user is logged in. If the user isn't log in, redirect to login page.
My issue is that configService.isLoggedIn() is using $http, meaning it's asynchronous and the rest of the code won't wait for it to be resolved.
So the question in short: I need the isLoggedIn() function to be resolved before continuing with any other code.
I've seen a lot on $q.defer() but I can't wrap my head around it.
Thanks in advance.
app.service('configService', ['$http', '$q', '$location', '$rootScope', function ($http, $q, $location, $rootScope) {
var self = this;
this.isLoggedIn = function () {
$http.get('/internalAPI.php?fn=login').then(function (result) {
if (result.data.isLoggedIn === true) {
$rootScope.isLoggedIn = true;
}
else {
$rootScope.isLoggedIn = false;
}
}, function() {
$rootScope.isLoggedIn = false;
});
return $rootScope.isLoggedIn;
}
}]);
app.service('navigationService', ['$rootScope', '$location', '$timeout', 'configService', function ($rootScope, $location, $timeout, configService) {
var self = this;
$rootScope.$on('$routeChangeStart', function (event, next, current) {
if (configService.isLoggedIn() !== true) {
// no logged in user, redirect to /login
if (next.templateUrl != "resources/views/login.php") {
$location.path("/login");
$rootScope.subTitle = 'Login';
}
//user is logged in but is trying to view the login page, redirect
} else if (next.templateUrl == 'resources/views/login.php') {
$location.path('/');
}
You can just return promise from your function and operate on this promise object. According to Angular $http documentation the $http object is based on promise mechanism
app.service('configService', ['$http', function ($http) {
var self = this;
this.isLoggedIn = function () {
return $http.get('/internalAPI.php?fn=login');
}
}]);
app.service('navigationService', ['$rootScope', '$location', '$timeout', 'configService', function ($rootScope, $location, $timeout, configService) {
var self = this;
$rootScope.$on('$routeChangeStart', function (event, next, current) {
configService.isLoggedIn().then(function(result) {
if (result !== true) {
// no logged in user, redirect to /login
if (next.templateUrl != "resources/views/login.php") {
$location.path("/login");
$rootScope.subTitle = 'Login';
}
//user is logged in but is trying to view the login page, redirect
} else if (next.templateUrl == 'resources/views/login.php') {
$location.path('/');
}
}
}
}
Many thanks for that. Went for the approach Rene M. mentioned regarding building an interceptor and let the server-side scripts handle the authentication.
Definitely does the trick, if the 403 status is returned then redirect the user to the login page and update the isLoggedIn variable. Refactoring the code to remove use of the rootScope, was a dirty workaround until I got a hang of the whole angular way of authentication.
Attached a simple example below in case anyone would stumble upon this in the future.
app.config(['$routeProvider', '$locationProvider', '$httpProvider', function ($routeProvider, $locationProvider, $httpProvider) {
$httpProvider.interceptors.push('responseObserver');
app.factory('responseObserver', ['$location', '$q', function ($location, $q) {
return {
'responseError': function (errorResponse) {
switch (errorResponse.status) {
case 403:
//Place logic here
break;
case 500:
//Place logic here
break;
}
return $q.reject(errorResponse);
}
};
}]);

Categories

Resources