10 digest() iteration reached error in login procedure - javascript

This is my code to implement in my application a login procedure. The application has to verify the cookie set by the server and continue with the login procedure by redirect the user to canvas state. My issue is that I get the above mentioned error. Actually it works that is the login is made successfully but I would like to get rid of this error. I guess that the error should be in the $stateChangeStart but I don't know how to fix it. any idea?
(function() {
'use strict';
angular
.module('app', [
'ui.router',
'ngResource',
'ngCookies',
'app.login'
])
.config(function($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise('/login');
})
.run(function($rootScope, AuthService, RedirectService) {
$rootScope.$on('$stateChangeStart', function(event, toState) {
if (!AuthService.isAuthenticated()) {
// the user isn't authenticated
event.preventDefault();
// redirect to the server side
RedirectService.redirectToAuth();
}
});
});
})();
(function() {
'use strict';
angular
.module('app.login')
.factory('AuthService', Auth);
Auth.$inject = ['Cookie', 'Session'];
function Auth(Cookie, Session) {
return {
login: function(params) {
// here set the session with params passed by the server
Session.create(params.id, params.data.id, params.data.make, params.data.name);
},
isAuthenticated: function() {
// check cookie here set in the server side
return Cookie.exist();
}
};
}
})();
(function() {
'use strict';
angular
.module('app.login')
.service('Cookie', Cookie);
Cookie.$inject = ['$cookies'];
function Cookie($cookies) {
this.authCookie = $cookies.__cookie;
this.exist = function() {
return (this.authCookie ? true : false);
};
}
})();
(function() {
'use strict';
angular
.module('app.login')
.factory('RedirectService', Redirect);
Redirect.$inject = ['$window'];
function Redirect($window) {
return {
redirectToAuth: function() {
// redirect the user to the server for auth
$window.location.href = "http://" + $window.location.host + "/auth/facebook";
}
};
}
})();
(function() {
'use strict';
angular
.module('app.login')
.controller('LoginController', Login);
// here inject what function Login needs
Login.$inject = ['$rootScope', '$scope', '$state', '$stateParams', 'AuthService'];
function Login($rootScope, $scope, $state, $stateParams, AuthService) {
var params = {
id: $stateParams.userid,
data: {
id: $stateParams.modelid,
make: $stateParams.modelmake,
name: $stateParams.modelname
}
};
$scope.login = function(params) {
AuthService.login(params);
// activate the canvas state
$state.go('canvas');
};
// run the login function to set the Session user with data passed by the server
$scope.login(params);
}
})();

Maybe this can help a little more:
/*
We are using the below urlRouterProvider.otherwise() because of:
https://github.com/angular-ui/ui-router/issues/600
*/
$urlRouterProvider.otherwise(function($injector, $location) {
var $state = $injector.get('$state');
$state.go('login');
});
With this code you can still use the otherwise(), the disadvantage of using when() is that other unknown routes will not match. Above code solved all of our infinite loops.

Fixed the issue
$urlRouterProvider.otherwise('/login');
caused all this issue.
removed it and replaced with when() solved the issue

Related

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.

Angular: Dealing with reload page and User Authentication

I've been working in a application that log a user. Once that the user is logged, the info of the user is stored in a service and a cookie is stored with the auth token.
Like this:
angular
.module('MyModule')
.service('AuthService', service);
service.$inject = ['$cookieStore', '$resource'];
function service($cookieStore, $resource){
var self = this,
user = null;
self.loginResource = $resource('my_path_to_login_resource');
self.login = login;
self.getUser = getUser;
self.reloadUser = reloadUser;
function login(_userCredentials){
var userResource = new self.loginResource(_userCredentials);
return userResource.$save()
.then(setUser);
}
function setUser(_userResponse){
user = _userResponse.toJSON();
}
function getUser(){
return user;
}
function reloadUser(_token){
return self.loginResource()
.get(_token)
.then(setUser);
}
}
Using ui-router when I need deal with the routes of the app I do this:
angular
.module('MyModule')
.run(runFn);
runFn.$inject = ['$state', '$rootScope', 'AuthService', '$cookieStore'];
function runFn($state, $rootScope, AuthService, $cookieStore) {
$rootScope.$on('$stateChangeSuccess', stateTransitioned);
function stateTransitioned(e, toState, toParams, fromState, fromParams) {
var pageRealoaded = fromState.name? false : true;
if(AuthService.getUser()){
//DEALING WITH STATES TRANSITIONS
}
else{
if($cookieStore.get('auth_token') && pageRealoaded){
//ENSURE THAT THE USER IS LOGGED WHEN THE STATE IS CHANGED
AuthService.reloadUser.then(function(){
$state.go(toState.name);
})
.catch($state.go.bind(null,'login'));
//I DON'T KNOW HOW AVOID THAT THE STATE IS LOADED UNTIL THE USER
//HAVE BEEN LOGGED
}
else{
$state.go('login');
}
}
}
}
When the page is reloaded, using the stored token, I try to waiting that user have been login, and then, if it's success, redirect to state toState.name, and if error, redirects to login.
My questions:
1. How to avoid that the state is loaded until the user have been login?
2. My architecture for dealing for this case are correct? Suggestions for better sctructure?
I would recommend you use the resolve functionallity of ui-router to to ensure that user is ALWAYS authenticated before entering a state.
See the ui-router wiki here:
https://github.com/angular-ui/ui-router/wiki#resolve
Something like this should prevent state entry before authentication.
.state('secure', {
resolve: {
user: function(AuthService, $state) {
// State will only be entered if this resolves.
return AuthService.resolveUser()
.catch(function(error) {
$state.go('login');
})
}
},
template: '<div>Secret Page</div>
})
I also threw together this pen with a working example:
http://codepen.io/marcus-r/pen/eZKbYV?editors=1010
Finally, for my first question, I create the following example that shows how I prevent that the state change until the user is logged successfully.
http://codepen.io/gpincheiraa/pen/zqLYZz
In this example, when i go to the "state3", i wait until the user is login, and then redirects to the state
angular
.module('exampleApp', ['ui.router']);
angular
.module('exampleApp')
.config(configFn);
configFn.$inject = ['$stateProvider', '$urlRouterProvider'];
function configFn($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise('state1');
$stateProvider
.state('state1', {
url: '/state1',
controller: ['$state', function($state){
var vm = this;
vm.current = $state.current.name;
vm.goTwo = function(){$state.go('state2');};
}] ,
controllerAs: 'one',
template: '<h1>{{one.current}}</h1><button ng-click="one.goTwo()">Go to state 2</button>'
})
.state('state2', {
url: '/state2',
controller: ['$state', function($state){
var vm = this;
vm.current = $state.current.name;
vm.goThree = function(){$state.go('state3');};
}] ,
controllerAs: 'two',
template: '<h1>{{two.current}}</h1><button ng-click="two.goThree()">Go to state 3</button>'
})
.state('state3', {
url: '/state3',
controller: ['$state', function($state){
var vm = this;
vm.current = $state.current.name;
}] ,
controllerAs: 'three',
template: '<h1>{{three.current}}</h1>'
})
}
angular
.module('exampleApp')
.run(runFn);
runFn.$inject = ['$rootScope','$timeout','$state'];
function runFn($rootScope, $timeout, $state){
var logged = false;
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
console.log(toState.name);
if(toState.name === 'state3' && !logged){
//Fake like a user take a 1 second to login in server
$timeout(function(){
logged = true;
console.log('login ok');
$state.go(toState.name);
},1000)
console.log('while the user not loggin, prevent the state change');
event.preventDefault();
}
})
}

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);
}
};
}]);

Pass $scope object to route, keep if refresh

Lets say i list all users in a list, when i click a user i want to route to a new view and get the data for the selected person.
What is the preferred way? Should i move the data i already got when i listed the users or should i create a new server call?
My first thought is to pass the data, but the problem with this is that the data the gets lost if the user refreshes the page.
What is the best practice to solve this?
Small example:
(function() {
var app = angular.module('app');
var controllerId = 'app.controllers.views.userList';
app.controller(controllerId, [
'$scope', 'UserService',function ($scope, userService) {
var vm = this;
vm.users = [];
userService.getAllUsers().success(function (data) {
vm.users= data.users;
});
var gotoUser = function(user) {
// Pass the user to UserDetail view.
}
}
]);
})();
<div data-ng-repeat="user in vm.users" ng-click="vm.gotoUser(user)">
<span>{{customer.firstname}} {{customer.lastname}}</span>
</div>
i now list the user details in UserDetail view, this view is now vulnerable against a browser refresh.
Typically most people just create a new server call, but I'll assume you're worried about performance. In this case you could create a service that provides the data and caches it in local storage.
On controller load, the controller can fetch the data from the service given the route params and then load the content. This will achieve both the effect of working on page refresh, and not needing an extra network request
Here's a simple example from one of my apps, error handling left out for simplicity, so use with caution
angular.
module('alienstreamApp')
.service('api', ['$http', '$q','$window', function($http, $q, $window) {
//meta data request functions
this.trending = function() {
}
this.request = function(url,params) {
var differed = $q.defer();
var storage = $window.localStorage;
var value = JSON.parse(storage.getItem(url+params))
if(value) {
differed.resolve(value);
} else {
$http.get("http://api.alienstream.com/"+url+"/?"+params)
.success(function(result){
differed.resolve(result);
storage.setItem(url+params,JSON.stringify(result))
})
}
return differed.promise;
}
}]);
I would say that you should start off simple and do a new server call when you hit the new route. My experience is that this simplifies development and you can put your effort on optimizing performance (or user experience...) where you will need it the most.
Something like this:
angular.module('app', ['ngRoute', 'ngResource'])
.factory('Users', function ($resource) {
return $resource('/api/Users/:userid', { userid: '#id' }, {
query: { method: 'GET', params: { userid: '' }, isArray: true }
});
});
.controller("UsersController",
['$scope', 'Users',
function ($scope, Users) {
$scope.loading = true;
$scope.users = Users.query(function () {
$scope.loading = false;
});
}]);
.controller("UserController",
['$scope', '$routeParams', 'Users',
function ($scope, $routeParams, Users) {
$scope.loading = true;
$scope.user = Users.get({ userid: $routeParams.userid }, function () {
$scope.loading = false;
});
$scope.submit = function () {
$scope.user.$update(function () {
alert("Saved ok!");
});
}
}]);
.config(
['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
$routeProvider
.when('/users', {
templateUrl: '/users.html',
controller: 'UsersController'
})
.when('/users/:userid', {
templateUrl: '/user.html',
controller: 'UserController'
})
.otherwise({ redirectTo: '/users' });
}
]
);

angular factory not available

angular.module('pipelineChromeApp', [
'ngResource',
'ngSanitize',
'ngRoute',
'LocalStorageModule'
])
angular.module('pipelineChromeApp')
.factory('Profile', [
'$http',
'apiUrl',
'localStorageService',
function ($http, apiUrl, localStorageService) {
var baseUrl = apiUrl + 'profile.json?api_key=';
return {
login: function(apiKey) {
return $http.get(baseUrl + apiKey);
},
logout: function() {
localStorageService.clearAll();
localStorageService.set('loggedIn', false);
console.log("cleared local storage");
return true;
}
}
}]);
angular.module('pipelineChromeApp')
.controller('LoginController', [
'$scope',
'Profile',
'$location',
function ($scope, Profile, $location) {
$scope.apiKey = "";
$scope.login = function(){
debugger
// Profile.login().then({
// $location.path( "/actions" );
// });
};
}]);
For some reason Profile isn't available in LoginController. Have I loaded things wrong?
As you mentioned in your comment, the problem you have is in the console, and I assume the browser you're using is chrome. In chrome/v8, the javascript engine tries very hard to optimize your code, it will remove any unused function parameters. In your case, Profile isn't used anywhere in your function, so v8 has removed it.
That's also the reason why it works fine when you have some code to do with the Profile.
Usually I will add a console.log() to an empty function if I want to check some variables in the chrome console, like this:
$scope.login = function() {
debugger;
console.log(Profile);
};

Categories

Resources