Issue with Angular Js resolve method - angularjs

Hi I have a $stateProvider like below.
$stateProvider .state('team.details', {
url: '/:teamId',
views: {
'#': {
controller: 'teamDetailsCtrl',
templateUrl: 'src/modules/team/details/teamDetails.html',
resolve: {
user: function ($stateParams, TeamResource) {
return TeamResource.get({ teamid: $stateParams.teamId }).$promise;
}
The TeamResource.get is calling a rest api and everything is working fine if the data is sent from the rest api(teamid present in DB). But if the data is not present then I have to route it to a "home". How can i accomplish it ?

You resolve could handle that thing, if data is there then return that data using chain promise or else redirect the to different state.
Code
resolve: {
user: function($stateParams, TeamResource, $state) {
return TeamResource.get({
teamid: $stateParams.teamId
}).$promise.then(function(data) {
if (!data.length > 0) { //here might be you need to use something else instead of data
$state.go('home')
return
} else {
return data;
}
});
}

What kind of response does your API return? My suggestion is that you set up a global http interceptor for your app which could listen to 404 responses. And in that case redirect users to your home state.
By putting the logic in an interceptor instead of the resolve, you can avoid redundant code if you have several states which may benefit from this kind of check.
This is an example interceptor, taken from this SO post.
Note that this may only be wanted for a specific endpoint (eg the teams). You can check which url got called by accessing the url property of response. Like so: response.url
var interceptor = ['$q', '$state', function ($q, $state) {
function success(response) {
return response;
}
function error(response) {
var status = response.status;
var url = response.url;
if (status == 404) {
$state.go('home')
return;
}
// otherwise
return $q.reject(response);
}
return function (promise) {
return promise.then(success, error);
}
}];
$httpProvider.responseInterceptors.push(interceptor);

Angular $http.get() returns a promise itself. if case of any error, you just need to reject the same with $q service.
In resolve:
resolve : {
user : function ($stateParams, TeamResource) {
return TeamResource.get({
teamid : $stateParams.teamId
});
}
}
Under TeamResource.get:
return {
get : function (teamObj) {
$http.get("/url", teamObj).then(function (resp) {
if (resp.length) {
return resp;
} else {
return $q.reject(resp);
}
}, function (err) {
return $q.reject(resp);
});
}
}
In case of return in promise() with $q.reject(), the promise chain sends it to error callback to the next chain and if you just return with something, it sends it to success function in next promise chain.

Related

Correct method to Redirect from $http.post in angularjs

Can you tell me what is the correct way to redirect to another page if $http.post returns a specific error code.
Just to add context, I want to redirect to another page if the user is not logged in or authorized to use the API.
function getData(filter) {
var deferred = $q.defer();
var data = JSON.stringify(filter);
$http.post('/myapp/api/getData', data)
.success(function (data, status, headers, config) {
deferred.resolve(data);
})
.error(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
You could do a redirect to the page using $window.location.href, based on the error condition you have.
var app = angular.module("sampleApp", []);
app.controller("sampleController", [
"$scope",
'$window',
'sampleService',
function($scope, $window, sampleService) {
sampleService.getData().then(function(result) {}, function(error) {
if (error.statusCode === 400) {
alert("Error");
$window.location.href = "http://stackoverflow.com"
}
});
}
]);
app.service("sampleService", function() {
this.getData = function() {
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
reject({
statusCode: 400
});
}, 1000);
});
return promise;
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-App="sampleApp">
<div ng-controller="sampleController">
</div>
</div>
The best way to catch global AuthenticationErrorin angular is with interceptor.
This way you can monitor all request that are sent from angular and check for AuthenticationError.
$provide.factory('AuthErrorInterceptor', function($q, $location) {
return {
'responseError': function(rejection) {
//check for auth error
$location.path('/login');
return $q.reject(rejection);
}
};
});
Example :
$http.post('/myapp/api/getData', data)
.then(function (data) {
if(data.ErrorCode==1)
{
$window.location.href="controllerName/actionName";
}
})
Use a interceptor service in order to centralize all of your rejection request in the same service.
module.config(['$httpProvider', ($httpProvider: ng.IHttpProvider) => {
$httpProvider.interceptors.push('errorService');
}]);
module.factory('errorService', ['$location', function($location) {
var errorService = {
responseError: function(rejection) {
if (rejection === '401') {
$location.path('/login');
}
}
};
return errorService;
}]);
The $http.post is misguiding.
So far the best answer is #Kliment's. Interceptors are the best way to manage what comes before and after http requests.
However, if your end goal is to prevent access to a page, you have to at least use a routing plugin (ngRoute, ui-router) because with the promise idea there will always be a delay between the http request and the response.
Depending on server response time you'll still see the page display for about a second or so.
With ui-router you simply configure a resolve method for each state you want to protect. It could look like this:
.state('protected',
{
url : '/protected_page',
templateUrl : 'secret.html',
resolve: {
loggedin: loggedin
}
})
loggedin refers to a function you define that contains your $http.post call (or better yet a service)
function loggedin($timeout, $q, $location, loginService) {
loginService.then(function(data) {
if(data.status == 401) {
//$timeout(function() { $location.path('/login'); });
return $q.reject();
} else {
return $q.when();
}
});
}
Here this particular service returns a 401 status but you can return anything.
The state will not be resolved (and the page not displayed) until it's accepted or rejected.
You can redirect directly from there if you want, although it's not very elegant.
ui-router gives you another option with default redirection:
if (tokenIsValid())
$urlRouterProvider.otherwise("/home");
else
$urlRouterProvider.otherwise("/login");
With otherwise you tell ui-router to go to certain urls if no state exists for a particular request or if a resolve has been rejected.
On another subject, your http request is badly written.
.success and .error are deprecated and you don't need to create a promise ($q) over an $http request that itself already returns a promise.
You have a good example in the documentation linked above.
You can redirect to page on unauthorized access of a user based on the status code which you can return from your API call.
$http({
method: "POST",
url: 'Api/login',
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
}).success(function (data,status) {
if(status==200){
alert('Successfully logged in');
$location.path('/dashboard'); //You can use this if you are defined your states.
}
}).error(function (data,status) {
if(status==403||status==403){ //Based on what error code you are returning from API
$location.path('/login');// if states are defined else
$window.location.href = "https://www.google.com";
}
});
First of all Nice Question , In this scenario You Can use $location , $state If it is external url You can use $window.location.href ... I would recommend $location and it is the best way ...
Please See the link for further Using $window or $location to Redirect in AngularJS
function getData(filter) {
var deferred = $q.defer();
var data = JSON.stringify(filter);
$http.post('/myapp/api/getData', data)
.success(function (data, status, headers, config) {
deferred.resolve(data);
if(data.errorcode==9999) // Define Your Error Code in Server
{
$location.path('/login'); // You Can Set Your Own Router
} })
.error(function (error) {
$location.path('/login'); // You Can Set Your Own Router
deferred.reject(error);
});
return deferred.promise;
}
Preferably use $location or $state ...

Resolve factory with result from other factory

Before a state is loaded, I wish to resolve a factory using the data from another factory.
My situation:
"taskVariables": function (getTask, getTaskVariables, $stateParams) {
getTask.get({'task': $stateParams.taskId}).success(function(data) {
console.log("I'm here");
return getTaskVariables.get({'processInstanceId': data.processInstanceId});
});
return false;
console.log("I have failed you master")
},
Factory:
.factory('getTask', function ($resource) {
return $resource('api/task/get/:task', {'user': '#user', 'task': '#task'}, {
'get': {
method: 'GET',
transformResponse: function (data) {
data = angular.fromJson(data);
return data;
}
}
})
})
The thing is that I think that one factory is loading faster than the other, thus the data will be undefined on page load, but that is just me speculating.
Is there a way to achieve this with maybe a promise or some kind?
Thanks in advance,
Because I return a $resource I cannot declare a success- nor a failure function, so you need to change it by giving it a $promise, followed by a then:
"taskVariables": function (getTask, getTaskVariables, $stateParams) {
return getTask.get({'task': $stateParams.taskId}).$promise.then(function(data) {
return getTaskVariables.get({'processInstanceId': data.processInstanceId});
});
}
reffering to this question/answer
You're forgetting to return the initial getTask promise.
"taskVariables": function (getTask, getTaskVariables, $stateParams) {
return getTask.get({'task': $stateParams.taskId}).success(function(data) {
console.log("I'm here");
return getTaskVariables.get({'processInstanceId': data.processInstanceId});
});
},

Getting user data in AngularJS on authentication and on page refresh

I've made an AuthService that should help me take care of all authentication matters like logging in, registering, getting user data from server etc.
I am looking for a solution that runs only on login, on page refresh and when triggered to refresh get the user data from the service and make it available on the controllers that i include the service to. I would like to have something like vm.user = AuthService.getUserData() and this returns the session variable from the service. Something like this:
function getUserData(){
if (session) {
return session.user;
}
return null;
}
On $rootScope.$on('$stateChangeStart' i have :
AuthService.loadSessionData();
Which translates to:
function loadSessionData() {
$http({
url: API.URL+'auth/session-data',
method: 'GET'
})
.success(function(response)
{
session = response;
})
.error(function(err){
console.log(err);
});
};
One of the issues here are that i have to set a timeout on AuthService.getUserData() because when this executes, the call that retrieves the session data from the server is not finished yet, but this is a bad practice.
Here is the complete service code http://pastebin.com/QpHrKJmb
How about using resolve? If I understood correctly you wish to have this data in your controller anyway.
You can add this to your state definitions:
.state('bla', {
template: '...'
controller: 'BlaCtrl',
resolve: {
user: ['AuthService', function(AuthService) {
return AuthService.loadSessionData();
}
}
}
also, alter your loadSessionData function: (make sure to inject $q to AuthService)
function loadSessionData() {
return $q(function(resolve, reject) {
$http({
url: API.URL + 'auth/session-data',
method: 'GET'
})
.success(function(response)
{
if (response) {
resolve(response);
} else {
reject();
}
})
.error(function(err){
reject(err);
});
})
}
Lastly, add the user object from the resolve function to you controller:
app.contoller('BlaCtrl', ['$scope', 'user', function($scope, user) {
$scope.user = user;
}]);
What does this accomplish?
In case the user does not have a valid session or an error occurs, the state change is rejected and the event $stateChangeError is thrown. You can listen (like you did for $stateChangeStart for that event and respond with a modal, redirect to an error page, or whatever.
You only pull the session for states that needs it - not for every state change.
The state is not resolved until the user data is resolved as well (either by resolve or reject).
You should call loadSessionData() in getUserData()
function getUserData(){
loadSessionData()
.success(function(response)
{
if (response) {
return response.user;
}
})
return null;
}
and
function loadSessionData() {
return $http.get(API.URL+'auth/session-data');
};

Angular JS: Service Response not Saving to Variable

I'm trying to work out why the response of this service isn't saving to $scope.counter. I've added a function to my service fetchCustomers(p) which takes some parameters and returns a number, which I'd like to save to $scope.counter.
service
angular.module('app')
.factory('MyService', MyService)
function MyService($http) {
var url = 'URL'';
return {
fetchTotal: function(p) {
return $http.get(url, { params: p })
.then(function(response) {
return response.data.meta.total;
}, function(error) {
console.log("error occured");
})
}
}
}
controller
$scope.counter = MyService.fetchTotal(params).then(function(response) {
console.log(response);
return response;
});
For some reason though, this isn't working. It's console logging the value, but not saving it to $scope.counter. Any ideas?
If I understand your question correctly, you're setting $scope.counter to a promise, not the response.
MyService.fetchTotal(params).then(function(response) {
// Anything dealing with data from the server
// must be put inside this callback
$scope.counter = response;
console.log($scope.counter); // Data from server
});
// Don't do this
console.log($scope.counter); // Undefined

How to return Firebase's rejected promise in Angular

I'm trying to return some data from the Rejected function of a Firebase promise in Angular to use in $routeChangeError. For some reason, the console.error(data) works, but the next line return data doesn't.
This is my code:
angular.module('campaignsHub.dashboard', ['ngRoute'])
.run(["$rootScope", "$location", "$route",
function($rootScope, $location, $route) {
$rootScope.$on("$routeChangeError", function(event, next, previous, error) {
// This works if I use return authService.auth.$requireAuth(); and nothing else
if (error === "AUTH_REQUIRED") {
$location.path("/login");
}
});
}
])
.config(['$routeProvider',
function($routeProvider) {
$routeProvider.when('/dashboard', {
templateUrl: 'dashboard/dashboard.html',
controller: 'DashboardCtrl',
resolve: {
"currentAuth": ["authService",
function(authService) {
authService.auth.$requireAuth().then(function(data) {
// RESOLVED
authService.GetCurrentUser().then(function(userData) {
return userData; // This works if user is logged in
})
}, function(data) {
// REJECTED
console.error(data); // This part works - returns AUTH_REQUIRED if user is not logged in
return data; // This part doesn't as it should be picked up by the .run() method
});
}
]
}
});
}
])
For a promise to work properly you have to return the promise from the function. Once the promise is resolved the calle get the resolved data which it can use to further process.
So you definitely need return statement for your authService inside resolve.
Change this
function(authService) {
authService.auth.$requireAuth().then(function(data) {
// RESOLVED
authService.GetCurrentUser().then(function(userData) {
return userData; // This works if user is logged in
})
}, function(data) {
// REJECTED
console.error(data); // This part works - returns AUTH_REQUIRED if user is not logged in
return data; // This part doesn't as it should be picked up by the .run() method
});
}
to
function(authService) {
return authService.auth.$requireAuth().then(function(data) {
// RESOLVED
authService.GetCurrentUser().then(function(userData) {
return userData; // This works if user is logged in
})
}, function(data) {
// REJECTED
console.error(data); // This part works - returns AUTH_REQUIRED if user is not logged in
return data; // This part doesn't as it should be picked up by the .run() method
});
}
I've managed to find a solution. To get it working, I expanded Shankar's solution and returned every promise in my resolve method. So now, it looks like this:
resolve: {
"currentAuth": ["authService",
function(authService) {
return authService.auth.$requireAuth().then(function(data) {
// RESOLVED
return authService.GetCurrentUser().then(function(userData) {
return userData;
})
}, function(data) {
// REJECTED
throw data;
});
}
]
}

Resources