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.
Related
this whole story is on Angular 1.5.5.
so I have created a component and on its controller, on the $onInit function which basically contains this request
apphelper.readFromConfig($http).then(function (config) {...});
Iam getting a [$http:badreq] Http request configuration url must be a string error
the apphelper is a typical javascript closure function that several different applications use to get predefined ajax calls and it looks like this
var apphelper = function () {
var config;
return {
readFromConfig: function ($http) {
return $http.get(this.getConfig()).
then(function (response) {
return response.data;
});
},
setConfig: function (_config) {
config = _config;
},
getConfig: function () {
return config;
}
}
} ();
now here is the extremely odd part
the setConfig() is fired inside a factory
app.factory('session', function GetSession($http, $q) {
return {
getConfig: function (configuration) {
var defer = $q.defer();
if (!configuration) {
configuration = "default";
}
$http({
url: "/config/" + configuration,
method: "GET",
data: "{}",
headers: { 'Content-Type': 'application/json' }
}).success(function (data, status, headers, config) {
//staff
apphelper.setConfig("/config/" + configuration);
defer.resolve('done');
}).error(function (data, status, headers, config) {
console.log(data);
defer.reject();
});
return defer.promise;
}
}
});
and this factory is fired from the routeprovider
app.config(['$routeProvider',
function ($routeProvider) {
$routeProvider.
when('/:config', {
templateUrl: 'partials/components/my-map.html',
controller: 'MapController',
resolve: {
configData: function (session, $route) {
return session.getConfig($route.current.params.config);
}
}
});
}]);
All this story is for two reasons
first I got a directive that needs some values before the dom is initialized (thus the resolve) thingy
second I need to check by the time the page component gets loaded if there were any changes in the config service
As odd as it might seem it works. Now i cannot figure out how to unit test this thing. Any help will be appreciated
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
There is a hidden page in my angular app, so only special id user can access it. To implement it I send HTTP get request to the server (because only the server knows that special id) when state changes to that hidden state.
And if server responses to me with an error message then I prevent state changing process, in other case user goes to that special page.
Here is the code I am using:
angular
.run(["$rootScope", "$location", "$state", "mySvc", function ($rootScope, $location, $state, mySvc) {
var id = mySvc.getId();
$rootScope.$on( '$stateChangeStart', function(event, toState , toParams, fromState, fromParams) {
if(toState.name === "specialstate") {
mySvc.check(id)
.then(function(result) {
if(result.error !== 0) {
event.preventDefault();
}
}, function(error) {
event.preventDefault();
});
}
});
}]);
Function in service:
function check(id) {
var deferred = $q.defer();
$http({
url: "/api/url",
method: "GET",
headers: {'Content-Type': 'application/json'}
}).
then(function (result) {
console.log(result);
if(result.data.error === 0) {
deferred.resolve(result.data);
}
else {
deferred.reject(result.data);
}
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
};
Everything is right, except one thing: the state is being changed without waiting for request result. I think I should use resolve, but I do not know how to use it properly.
Thanks.
ui-router supports a resolve object that defines aset of data that should be resolved (from server, for instance), before the state transition is allowed.
.state('hidden', {
url: '/hidden',
...
resolve: {
data: function(mySvc) {
var id = mySvc.getId();
return mySvc.check(id).then(function(result) {
if (result.error !== 0) {
throw new Error("not allowed");
}
});
}
}
});
The data object defined would be available to your controller in that hidden state, and only in case it returns a 2xx status code and there's an error property equal to 0.
If data is resolved, then a state change occurs and a controller is instantiated. In case of rejection, none of this takes place.
As a personal note, I find that a great device to handle authorization.
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.
I am new to AngularJS & working on a sample. In my sample app I have an MVC Web api (which returns some data from db) & it will be called from the Angular Services and returns the data to the Controller. The issue is I am getting the data in my Services success method properly but in my controller it always shows undefined & nothing is displayed in the view. Please see the code below:
My Controller code:
app.controller('CustomerController', function ($scope, customerService) {
//Perform the initialization
init();
function init() {
$scope.customers= customerService.getCustomers();
}
});
My Services code:
app.service('customerService', function ($http){
this.getCustomers = function () {
$http({
method: 'GET',
url: 'api/customer'
}).
success(function (data, status, headers, config) {
return data;
}).
error(function (data, status) {
console.log("Request Failed");
});
}
});
Please help me to fix this issue.
That's because your service defines the function getCustomers but the method itself doesn't actually return anything, it just makes an http call.
You need to provide a callback function in the form of something like
$http.get('/api/customer').success(successCallback);
and then have the callback return or set the data to your controller. To do it that way the callback would probably have to come from the controller itself, though.
or better yet, you could use a promise to handle the return when it comes back.
The promise could look something like
app.service('customerService', function ($http, $q){
this.getCustomers = function () {
var deferred = $q.defer();
$http({
method: 'GET',
url: 'api/customer'
}).
success(function (data, status, headers, config) {
deferred.resolve(data)
}).
error(function (data, status) {
deferred.reject(data);
});
return deferred;
}
});
Your problem is in your service implementation. You cannot simply return data since that is in the asynchronous success callback.
Instead you might return a promise and then handle that in your controller:
app.service('customerService', function ($http, $q){
this.getCustomers = function () {
var deferred = $q.defer();
$http({
method: 'GET',
url: 'api/customer'
})
.success(function (data, status, headers, config) {
// any required additional processing here
q.resolve(data);
})
.error(function (data, status) {
q.reject(data);
});
return deferred.promise;
}
});
Of course if you don't require the additional processing, you can also just return the result of the $http call (which is also a promise).
Then in your controller:
app.controller('CustomerController', function ($scope, customerService) {
//Perform the initialization
init();
function init() {
customerService.getCustomers()
.then(function(data) {
$scope.customers= data;
}, function(error) {
// error handling here
});
}
});
VERY late answer, but, Angular's $http methods return promises, so there's no need for wrapping everything into promise form with $q. So, you can just:
app.service('CustomerService', function ($http) {
this.getCustomers = function () {
return $http.get('/api/customer');
};
});
and then call the .success() or .error() shortcut methods in your client controller.
If you want to take it a step further and have a fully-fledged RESTful CustomerService without having to write this boilerplate, I'd recommend the restangular library, which makes all sorts of methods available to you - assuming of course your backend responds to HTTP verbs in the "standard fashion".
Then you could just do this:
app.service('CustomerService', function (Restangular) {
return Restangular.service('api/customer');
});
and call the methods Restangular makes available to you.
I use this for communication between Angular Web Data Service and Web Api Controller.
.factory('lookUpLedgerListByGLCode', function ($resource) {
return $resource(webApiBaseUrl + 'getILedgerListByGLCode', {}, {
query: { method: 'GET', isArray: true }
});
})
OR
.factory('bankList', function ($resource) {
return $resource(webApiBaseUrl + 'getBanklist_p', {}, {
post: {
method: 'POST', isArray: false,
headers: { 'Content-Type': 'application/json' }
}
});
})