Angular resolve when using ui-router - angularjs

I am sure this is a configuration error, but not sure how to get round it correctly with angular.
I have my states setup like so
$stateProvider
.state('app', {
url: '',
abstract: true,
templateProvider: ['$templateCache', function ($templateCache) {
return $templateCache.get('app/main/main.html');
}],
controller: 'MainController',
resolve: {
Pages: ['PageFactory', function (PageFactory) {
return PageFactory.getAll();
}]
}
})
.state('app.home', {
url: '/home',
views: {
'content#app': {
templateProvider: ['$templateCache', function ($templateCache) {
return $templateCache.get('app/page/page.html');
}],
controller: 'PageController'
}
},
resolve: {
Page: ['$stateParams', 'PageFactory', function ($stateParams, PageFactory) {
return PageFactory.get($stateParams.id);
}],
ModuleData: function () {
return {};
},
Form: function () {
return {};
}
}
});
Now my problem is that if PageFactory.get($stateParams.id) fails, then the page is reloaded, and reloaded and reloaded.
Here is a sample from PageFactory, which as you can see returns a promise
angular.module('app').factory('PageFactory', ['$http', '$q', function ($http, $q) {
var urlBase = 'api/page';
var factory = {};
factory.getAll = function () {
var $defer = $q.defer();
$http.get(urlBase)
.success(function (data) {
$defer.resolve(data);
})
.error(function (error) {
$defer.reject(error);
});
return $defer.promise;
};
factory.get = function (id) {
var $defer = $q.defer();
$http.get(urlBase + '/' + id)
.success(function (data) {
$defer.resolve(data);
})
.error(function (error) {
$defer.reject(error);
});
return $defer.promise;
};
}]);
It is possible to restrict the number of times the resolve is attempted, or should i have set this up differently in the first place?
I noticed this when moving the site from 1 place to another and needed to specify a base href in my index.html page. Because the $http was trying to connect to a url that didnt exist, it just kept on trying and trying and trying, which if someone left it would harn our web servers performance.
My state app is my route state (abstract state) and the user defaults to app.home uponing entering the site. I guess this is why it just keeps retrying the resolve?

We also had infinite loops when there were errors in resolves, so we ended up having more logic in the $stateChangeError event handler. Since we have this, we had no more troubles with loops.
See how we check where we were trying to go to and if we failed while going to a home state, we do not try again and redirect to a simple error state instead.
Here our example, this is setup in our main module's run method:
$rootScope.$on("$stateChangeError", function (event, toState, toParams, fromState, fromParams, error) {
console.log('Error on StateChange from: "' + (fromState && fromState.name) + '" to: "'+ toState.name + '", err:' + error.message + ", code: " + error.status);
if(error.status === 401) { // Unauthorized
$state.go('signin.content');
} else if (error.status === 503) {
// the backend is down for maintenance, we stay on the page
// a message is shown to the user automatically by the error interceptor
event.preventDefault();
} else {
$rootScope.$emit('clientmsg:error', error);
console.log('Stack: ' + error.stack);
// check if we tried to go to a home state, then we cannot redirect again to the same
// homestate, because that would lead to a loop
if (toState.name === 'home') {
return $state.go('error');
} else {
return $state.go('home');
}
}
});

The common way to use resolve with promises is to use $q.defer()
Pages: ['PageFactory', '$q', function (PageFactory, $q) {
var deffered = $q.defer();
PageFactory.getAll()
.success(function(data) {
deffered.resolve(data);
})
.error(function(data) {
deffered.reject();
})
return deferred.promise;
}]
This will reject the the state change if it fails, or you can do whatever when it fails. And it passes the data through if it succeeds.
Also see this post about the same thing.
Angular ui-router get asynchronous data with resolve

Related

angularjs- Why http request returns false with resolve?

I have a login system...and I'm trying to implement login/logout feature to a website.
This is the fiddle I'm following- FIDDLE
This is my route file:
(function() {
var onlyLoggedIn = function($location, $q, AuthService2) {
var deferred = $q.defer();
if (AuthService2.isLogin()) {
deferred.resolve();
} else {
deferred.reject();
$location.url('/login');
}
return deferred.promise;
};
angular.module('myApp', [
'ngRoute',
'myApp.login',
'myApp.home',
'myApp.logout',
'myApp.notifications'
])
.factory('AuthService2', ["$http", "$location", function($http, $location) {
//var vm = this;
var baseUrl = 'api/';
return {
isLogin: function() {
var token;
if (localStorage['entrp_token']) {
token = JSON.parse(localStorage['entrp_token']);
} else {
token = "";
}
var data = {
token: token
};
$http.post(baseUrl + 'validateUserToken', data).success(function(response) {
if (response.msg == "authorized") {
//console.log(response.msg);
return localStorage.isLogged === "true";
} else {
return localStorage.isLogged === "false";
}
});
}
}
return {
isLogin: isLogin
};
}])
.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/login', {
controller: 'LoginController',
templateUrl: 'app/components/login/loginView.html',
controllerAs: 'vm'
})
.when('/home', {
controller: 'HomeController',
templateUrl: 'app/components/home/homeView.html',
resolve: {
loggedIn: onlyLoggedIn
},
controllerAs: 'vm'
})
.when('/logout', {
controller: 'LogoutController',
templateUrl: 'app/components/login/loginView.html',
resolve: {
loggedIn: onlyLoggedIn
},
controllerAs: 'vm'
})
.when('/notifications', {
controller: 'NotificationsController',
templateUrl: 'app/components/notifications/notificationsView.html',
resolve: {
loggedIn: onlyLoggedIn
},
controllerAs: 'vm'
})
.otherwise({
redirectTo: '/login'
});
}]);
})();
Users will be visiting login page. After authentication, I set a session and a local storage token.
I use resolve to check whether the user is valid or not. I defined a function for that (as you can see it from the code).
Upon login, I'm able to validate the user and set the session and login controller redirects the valid user to home. But the user reaches the route and he stays in login page itself.
My http request is fine. I checked it and it returns correct result. But I think the function returns false and afterthat only the http request is getting executed. because I can see the http request in consoel and it returns positive result, so the user should get navigated but it's not happening because http request is getting delayed or something.
I tried giving an alert message inside this if else,and it always goes to else condition, no matter what.
if (AuthService2.isLogin()) {
deferred.resolve();
} else {
deferred.reject();
$location.url('/login');
}
Why is it so? I'm pretty much new to ANgular. Is this a limitation of angular?
The problem is that there is no return statement in your isLogin function.
Your return statement is inside the $http.post callback NOT isLogin hence isLogin return undefined thus resolved to falsy.
$http.post(baseUrl + 'validateUserToken', data).success(function(response) {
//Notice that you are inside function(response) closure.
if (response.msg == "authorized") {
return localStorage.isLogged === "true";
} else {
return localStorage.isLogged === "false";
}
});
I can suggest you to return $http.post instead, $http.post return a promise object which you can use in such way, below is an brief example on how you can do it.
isLogin: function() {
//Omitted some code
//This method return a promise object
return $http.post(baseUrl + 'validateUserToken', data);
});
AuthService2.isLogin().then(function(res){
if (response.msg == "authorized") {
deferred.resolve();
} else {
deferred.reject();
$location.url('/login');
}
}, errorCallback); //Optional if you want to handle when post request failed
Code is not tested, just to give you the idea.

angularjs redirect to login page if not authenticated with exceptions

EDIT: forgot to mention that i've been working with AngularJs for a week only, so if you see something you think should be changed for the better and is not related to the question itself feel free to tell me on the comments section.
ok, so I have my authentication Controllers and providers which I won't show because they're irrelevant for the scope of the question.
Then I have an interceptor to check if the user is authenticated when a Call is made. If so I set the Authentication header on the request to include the user's Token if not I redirect the user to the login page and don't even make the request to the server (obviously if someone bypasses this theres also an Authorize on the API).
What I want is to add a few exceptions, meaning there are some pages I want to allow even if the user has no Auth Token. I'm able to this if it's a specific path, but I want to allow my 404 page to be accessed and it's in the Routing that I'm specifying .otherwise to go to the 404 page, how can I make so that my interceptor only redirects to login if it's not going to this page.
The interceptor
.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var authData = localStorageService.get('authorizationData');
var _request = function (config) {
config.headers = config.headers || {};
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
} else if ($location.path != '/accounts/login' && $location.path != '/accounts/register') {
$location.path('/accounts/login');
}
return config;
}
var _responseError = function (rejection) {
if (rejection.status === 401) {
$location.path('/accounts/login');
}
return $q.reject(rejection);
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
return authInterceptorServiceFactory;
}])
and in my Routing
$urlRouterProvider.otherwise('/page-not-found');
$stateProvider
(...)//rest of the states
.state('page-not-found', {
url: '/page-not-found',
templateUrl: '/Content/partials/error/404.html',
data: {
displayName: false
}
})
(...)//rest of the states
I tried to add '/page-not-found' to my if but it won't work as expected because by the time the location is checked for the first time it's still not redirected.
edit
As sugested by charlietfl I'm now trying to use resolve but it's not even passing my function.
I removed this code from my interceptor:
else if ($location.path != '/accounts/login' && $location.path != '/accounts/register') {
$location.path('/accounts/login');
}
and add a new service to the authentication module:
.service('authCheckService', ['$http', '$q', 'localStorageService', function ($http, $q, localStorageService) {
var self = {
'onlyLoggedIn': function ($state, $q) {
var deferred = $q.defer();
var authData = localStorageService.get('authorizationData');
console.log(authData);
if (authData) {
deferred.resolve();
} else {
deferred.reject();
$state.go('login');
}
return deferred.promise;
}
}
return self;
}]);
and i'm trying to call it as:
.state('smo-dashboard', {
url: '/dashboard',
templateUrl: '/Content/partials/dashboard.html',
resolve: authCheckServiceProvider.onlyLoggedIn
})
notice that i'm trying to log authData var to check if it's working but it isn't and there's no error on the console also.
Finally figured out how to solve it using resolve.
first of all I completely removed the interceptor I was using before.
then I made a function inside my Routing .config to use with every resolve for the authentication. finally to handle my resolve I'm using $stateChangeError to redirect to the login state
the Routing Config
.config(function ($stateProvider, $urlRouterProvider) {
// function to check the authentication //
var Auth = ["$q", "authService", function ($q, authService) {
authService.fillAuthData;
if (authService.authentication.isAuth) {
return $q.when(authService.authentication);
} else {
return $q.reject({ authenticated: false });
}
}];
/* if the state does not exist */
$urlRouterProvider
.otherwise('/page-not-found');
$stateProvider
// state that allows non authenticated users //
.state('home', {
url: '/',
templateUrl: '/Content/partials/home.html',
})
// state that needs authentication //
.state('smo-dashboard', {
url: '/dashboard',
templateUrl: '/Content/partials/dashboard.html',
resolve: {
auth: Auth
}
})
// errors //
.state('page-not-found', {
url: '/page-not-found',
templateUrl: '/Content/partials/error/404.html'
})
// accounts //
.state('login', {
url: '/accounts/login',
templateUrl: '/Content/partials/account/login.html'
})
// OTHER STATES //
}
);
in the MainController
$scope.$on("$stateChangeError", function (event, toState, toParams, fromState, fromParams, error) {
$state.go("login");
});
An error service like this could help to handle what to do according to status in responses:
'use strict';
/**
* Error Service
*/
angular.module('app.errorService', [])
.factory("errorService", function ($route, $location) {
return {
checkAndReturnError: function(a,b,c) {
if (a.status === 401){
(function(){
return $location.path("/accounts/login");
}());
return;
}
if (a.status === 404)
return;
alert("Error \n *" + a.data.message);
}
};
});
Then when you do your calls if the response status is 401 it will redirect. The vbad thing agout this is you have to add it to all calls:
$scope.pageChanged = function() {
$scope.Promise = Resource.get({}, function(response) {
}, errorService.checkAndReturnError);
};

AngularJS Resolve a route based on a service promise

I am using ngRoute and and trying to get my routes to resolve based on the result of a function in a service I have defined
My service is as follows:
app.factory('AuthService', function ($q,$location,$http) {
var isLoggedIn = false;
return {
hasLoginSession: function(){
var defer = $q.defer();
if(isLoggedIn) {
//User has a valid session from a previous GetSession.json request
defer.resolve(isLoggedIn);
} else {
return $http.get('/session/GetSession.json').success(function(data, status, headers, config) {
isLoggedIn = data.success;
if(isLoggedIn) {
defer.resolve(isLoggedIn);
}
else {
defer.reject("EX_LOGIN_SESSION_IS_UNKNOWN");
}
}).
error(function(data, status, headers, config) {
isLoggedIn=false;
defer.reject("EX_LOGIN_SESSION_IS_UNKNOWN");
});
}
return defer.promise;
}
};
});
So as you can see I just have a simple session check function which sets a property based on the result of a http request.
I then have the routing setup like so, with a resolve just on the route path for testing at the moment:
var app = angular.module('pinpointersApp', ['ngRoute']);
app.config(
function($routeProvider,$httpProvider) {
//$httpProvider.interceptors.push(interceptor);
$routeProvider.
when('/login', {
templateUrl: 'login.html',
controller: 'LoginController'
}).
when('/map', {
templateUrl: 'partials/map.html',
controller: 'MapController'
}).
when('/locations', {
templateUrl: 'partials/locations.html',
controller: 'LocationsController'
}).
when('/', {
templateUrl: 'partials/locations.html',
controller: 'LocationsController',
resolve: {
checkSession: function ($q,AuthService) {
//var defer = $q.defer();
//defer.reject("EX_LOGIN_SESSION_IS_UNKNOWN");
//return defer.promise;
return AuthService.hasLoginSession();
}
}
});
});
app.run(['$rootScope', 'AuthService', function ($rootScope, AuthService) {
$rootScope.$on("$routeChangeError", function (event, current, previous, error) {
console.log(error);
//Perform other stuff here, e.g. redirect to login view
});
}]);
The server side request is being made, and I am seeing a pause in the view loading until the response is received. In my test I am returning a fail case and so reject the state of the promise in order to cause the $routeChangeError to be fired, but it never does and my view continues to load.
If I use the commented out lines of test code in my resolve block instead of my service routine call, so
resolve: {
checkSession: function ($q,AuthService) {
var defer = $q.defer();
defer.reject("EX_LOGIN_SESSION_IS_UNKNOWN");
return defer.promise;
//return AuthService.hasLoginSession();
}
}
then the routeChangeError event is fired, so what am I missing in order to just use the result of my service routine call?
OK, I figured out what I did wrong, in my service I had one too many return statements, I just needed to remove the return keyword before my opening $http request and now it works as required.

How to redirect with $state.go

I'm trying to redirect my user to Dashboard after Third Party Login. But when success callback is fired, the application still on Login Page, nothing happened. If I refresh the browser my interceptor catch already login and change to Dashboard... My Login Controller looks like this:
ThirdParty.login(function(result){
callbackSUCCESS(result);
},function(){});
function callbackSUCCESS(result){
AuthenticationService.login(result).then(
callbackServerSUCCESS(), function(reject) {
callbackServerERROR(reject);
});
}
function callbackServerSUCCESS() {
$scope.$apply(function() {
$state.go('dashboard');
});
}
My route in app.js
$stateProvider
.state('dashboard', {
url: '/dashboard',
views: {
'': {
templateUrl: 'views/dashboard/dashboard.html',
controller: 'DashboardCtrl'
}
}
});
My Header Controller
.controller('HeaderCtrl', ['$scope', 'AuthenticationService', '$state',
function($scope, AuthenticationService, $state) {
$scope.logout = function() {
AuthenticationService.logout().then(callbackServer(), callbackServer());
};
function callbackServer() {
$state.go('login');
}
}
]);
Authentication Controller Angular Factory
var headersConfig = {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache'
};
return {
login: function(credentials) {
var deferred = $q.defer();
$http.post('/api/users/sign_in', sanitizeCredentials(credentials), {
headers: headersConfig,
timeout: deferred.promise
}).then(function(result) {
if (result.status === 200) {
UserSessionService.cacheSession(result.data);
deferred.resolve();
} else {
deferred.reject();
}
}, function(reject) {
UserSessionService.clean();
deferred.reject(reject);
});
$timeout(function() {
deferred.resolve();
}, 15000);
return deferred.promise;
}
};
I can't remember the exact semantics of $state.go, but usually you need to use $scope.$apply in some manner when responding to events Angular isn't aware of, to ensure a digest cycle occurs. I.e. you could try:
ThirdParty.login(function(result) {
$scope.$apply(function() {
$state.go('dashboard');
});
}, function() {});
You need to get a reference to a scope from somewhere, but one shouldn't be hard to find in an Angular app.
Get rid of both () in
then(callbackServer(), callbackServer())

AngularJS handle rejected response with UI-Router

I have a Service which wraps my API calls in Angular:
var ConcernService = {
list: function (items_url) {
var defer = $q.defer();
$http({method: 'GET',
url: api_url + items_url})
.success(function (data, status, headers, config) {
defer.resolve(data, status);
})
.error(function (data, status, headers, config) {
defer.reject(data, status);
});
return defer.promise;
},
Then my app config, with UI-Router:
.config(function($stateProvider){
$stateProvider
.state('default', {
url: '/',
resolve: {
tasks: function ($stateParams, ConcernService) {
return ConcernService.list('tasks/').then(
function (tasks) { return tasks; },
function (reason) { return []; }
);
},
...
}
}
});
This is the most basic configuration I could get away with, which basically just returns an empty object if a 403, 404 etc is encountered and I can handle that in the view, template.
My question is, what is the best approach for getting the other detail to the view/ template, such as the rejection reason and status. Should it be returned in the tasks object, or separately?
Well, first of all, your first bit of code has the deferred anti pattern, let's fix that:
list: function (items_url) {
return $http.get(api_url + items_url); // $http already returns a promise
},
Also note, that deferred rejections and fulfillments are single value, so your multiple return values don't really work here.
Now, let's look at the router. First of all, your first fulfillment handler is redundant.
.state('default', {
url: '/',
resolve: {
tasks: function ($stateParams, ConcernService) {
return ConcernService.list('tasks/'); // result.data contains the data here
},
...
}
Now, the problem we have here is what happens in case of a rejection?
Well, listen to $stateChangeError:
$rootScope.$on('$stateChangeError',
function(event, toState, toParams, fromState, fromParams, error){ ... })
Here, you can listen to change state failures from rejections.

Resources