Angular return promise from httpBackend.when() - angularjs

How do you return a promise from httpBackend.when()? I wanted to load some canned data stored in a .json file and return that from httpBackend.whenGET(). When I try to return the promise from http.get('mydata.json') the response is returned to the failure callback of the factory.
function getAvailablePackagesComplete(response) {
return response.data;
}
function getAvailablePackagesFailed(error) { // {error = Object {data: undefined, status: 0, config: Object, statusText: ""}
$log.error(error.data.description);
return false;
}
function getAvailablePackages() {
return $http.get('/1.0/get-available-packages')
.then(getAvailablePackagesComplete)
.catch(getAvailablePackagesFailed)
}
var data = {"package": "test", "version": "1"}
$httpBackend.whenGET('/1.0/get-available-packages').respond(function(method, url, data) {
// return [200,data, {}] // this works
return $http.get('app/home/fixtures/mydata.json'); // contains {"package: "test", "version": "1"}
}); //this doesn't work

As it is currently, $httpBackend (from ngMockE2E) does not support promises within its .respond - See AngularJS GitHub Issue #11245. As $httpBackend should be used to avoid making real HTTP requests, but you could let some requests pass through.
From AngularJS Docs:
This implementation can be used to respond with static or dynamic responses via the when api and its shortcuts (whenGET, whenPOST, etc) and optionally pass through requests to the real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch templates from a webserver).
To work around what you're trying to do though, you could try to have getAvailablePackages() return the HTTP GET for your json file path and defining an $httpBackend.whenGET('pathTo.json').passThrough();

I was hitting the same issue and my use case was building a mock of my entire API in JS so that other people could work off line and develop the UI.
To achieve that I have developed a plugin called angular-mocks-async which decorates the httpBackend and adds the .whenAsync( ) APi to it. Than you can easily mock responses and return promises like so:
var app = ng.module( 'mockApp', [
'ngMockE2E',
'ngMockE2EAsync'
]);
app.run( [ '$httpBackend', '$q', function( $httpBackend, $q ) {
$httpBackend.whenAsync(
'GET',
new RegExp( 'http://api.example.com/user/.+$' )
).respond( function( method, url, data, config ) {
var re = /.*\/user\/(\w+)/;
var userId = parseInt(url.replace(re, '$1'), 10);
var response = $q.defer();
setTimeout( function() {
var data = {
userId: userId
};
response.resolve( [ 200, "mock response", data ] );
}, 1000 );
return response.promise;
});
}]);

You can return promises from http interceptors. Here is an example of delaying an HTTP call for 1 second. You can add this in your app.config(....)
$httpProvider.interceptors.push(function($q, $timeout) {
return {
'response': function(response) {
var defer = $q.defer();
$timeout(function() {
defer.resolve(response);
}, 1000);
return defer.promise;
}
};
});

Related

How can I save JSON data from an API service to cache?

I call the API service from the backend side. I want to save the JSON return data to the cache. What should I do? Is my format wrong?
The snippet code here:
// var localData = [];
var service = {
search: function(requestId) {
return $http.get('/api/mace/getRequest', {
params: {
id: requestId
},
cache:true
});
},
return service
I can't see that there's something wrong with your code. You did correctly set the cache variable to true in the http-call as described in the documentation from AngularJS.
$http.get(url, {
cache: true
}
You should get your JSON by calling your service function and waiting for the promise:
yourService.search(yourRequestId).then(function(response) {
console.log('result', response.data);
});

Custom cache for HTTP requests

I want to cache the response [i.e. parsed JSON response] of HTTP requests rather than the response itself. My data is big and gzipped so there is actually a fair performance hit decompressing this so would like to store the raw data itself.
Currently I am using a HTTP Interceptor for caching and TimeToLive mechanics described here alongside AngularJS' built in $cacheFactory.
So how can I, using an intercepter, stop the HTTP request and return my own response. Note I still plan on using $cacheFactory, I'd just manage my own data.
.factory('cacheInterceptor', ['$cacheFactory', function($cacheFactory) {
return {
request: function(config) {
if (config.cache) {
// if we have stored this request, return it, else let the request happen naturally and cache after
// Things I don't know:
// How to return existing cache data and prevent the reqeust from happening
// Cache the data I get back from a HTTP request
}
return config;
}
};
}])
I would preffer to inject this into your service and make your factory only handle the data recived/cached. This time I only created a service for you which holds the logic of HTTP / Cache switch. I think you will be able to create a factory to handle your data/states on your own.
.service('getService', ['$cacheFactory', '$http', '$q', function($cacheFactory, $http, $q) {
return {
request: function() {
function getData () {
var deferred = $q.defer();
if (angular.isUndefined($cacheFactory.get('getServiceData'))) {
$http({
'method': 'GET',
'url': 'someUrl'
}).then(function (result) {
$cacheFactory.put('getServiceData', result.data);
deferred.resolve(result.data);
});
} else {
deferred.resolve($cacheFactory.get('getServiceData'));
}
}
return getData();
},
flush: function () {
$cacheFactory.remove('getServiceData');
},
refresh: function () {
this.flush();
return this.refresh();
}
};
}]);
it's enough to add {cache: true} to the request options.
see the here
$http.get('some/url', {cache: true})
.then( ({data}) => data)

Angular JS global config for resource success query

I have implemented resources in my single page angular app which fires to my REST client server. I have made different services for each resource. Now my REST server is sending a value in response header, now I want to know a proper way where I can retrieve that value from headers.
My service code:
app.service('$job', function($resource) {
var job = $resource(service_base_url+'jobs.json/:id');
return job;
});
My controller which is getting headers:
app.controllerProvider.register('JobPostsController',['$scope','$job', function($scope, $job) {
$scope.jobs = {};
$scope.job_titles = {};
$job.query(function(jobs,responseHeaders){
var headers = responseHeaders();
some_function(headers.user);
$scope.jobs = jobs.jobs;
});
}
]);
I am getting headers in my above code, but I don't want to inject it in all controllers. So is there a proper way to do it? Some single config code which will run for all future resources request or some kind of event which can be only triggered when successful resource response with 200 OK
Try interceptor.
I don't know exactly your logic. You could register a global interceptor which intercepts all requests:
angular.module('App', [])
.config(function ($httpProvider){
$httpProvider.interceptors.push(function() {
return {
'response': function(response) {
var headers = response.headers();
some_function(headers.user);
return response;
}
};
});
});
or just register an interceptor which runs only for all requests of this query.
app.service('$job', function($resource) {
var job = $resource(service_base_url+'jobs.json/:id',{}, {
'query': {
method:'GET',
isArray:true,
interceptor: {
'response': function(response) {
var headers = response.headers();
some_function(headers.user);
return response;
}
}
}
});
return job;
});
Side notes:
Should not use $ prefix for your service name as it's reserved for angular, it may conflict with angular future versions.
I guess you need .factory instead of .service
You can set up the service in a run block like:
angular.module('myApp', [])
.run(['$rootScope', '$job',function ($rootScope, $job) {
$rootScope.jobs = {};
$rootScope.job_titles = {};
$job.query(function(jobs,responseHeaders){
var headers = responseHeaders();
some_function(headers.user);
$rootScope.jobs = jobs.jobs;
});
}]);
The only drawback your service is global to the app
I would go with a base service factory. This would allow you to have common service related functionality in one place
app.factory('ServiceBase', function () {
function ServiceBase() {
this.responseHeaders = function responseHeaders(resp){
// todo
};
}
return ServiceBase;
});
app.service('$job', function($resource, ServiceBase) {
var service = function () {
// $job related functions here
};
angular.extend(service, new ServiceBase());
return service;
});
Now anything in the ServiceBase is accessible to the controller and to the service. This allows you to have common functionality, has no new injection dependencies (on the controller), and is easy to extend further.
I think angulrjs response interceptors can help u for this.

Angularjs how to cancel resource promise when switching routes

I'm just getting my feet wet with Angularjs. I have an issue which I think has something to do with promises.
Let's say I load route 'A' which makes several ajax requests through it's controller:
allSites = AllSites.query({ id:categoryID });
allSites.$promise.then(function(allSites){
//add stuff to the scope and does other things
//(including making another ajax request)
});
Then I have route 'B' which makes it's own API request through it's controller:
$scope.categories = Category.query();
Here's the factory service currently used by route 'A':
.factory('AllSites',function($resource){
return $resource('api/categorySites/:id');
});
When I first view route 'A' but then switch to 'B' before 'A' is finished loading, route 'B' sits and waits for everything initially requested in 'A' to finish (actually, the query() request is made, but it won't resolve until the one from 'A' does, at that point, the stuff inside .then() continues to happen, even though I don't need it as I'm now on another route.
As you can see in my devtools timeline, the green line indicates when I switched to route 'B'. The request for route 'B' didn't resolve until the two requests above did (a request that is usually very fast). (at which point I'm able to use the view as a user). Then, after that, more promises resolve from route 'A'.
I've searched everywhere for an answer and can only find people that want to "defer" the route loading until promises are resolved. But in my case I almost want the opposite. I want to kill those requests when I switch.
Here's someone else with the same, unanswered question: Reject Angularjs resource promises
Any help is appreciated.
First of all, I decided I needed to use $http since I couldn't find any solution that used $resource, nor could I get it to work on my own.
So here's what my factory turned into, based on #Sid's answer here, using the guide at http://www.bennadel.com/blog/2616-aborting-ajax-requests-using-http-and-angularjs.htm
.factory('AllSites',function($http,$q){
function getSites(categoryID) {
// The timeout property of the http request takes a deferred value
// that will abort the underying AJAX request if / when the deferred
// value is resolved.
var deferredAbort = $q.defer();
// Initiate the AJAX request.
var request = $http({
method: 'get',
url: 'api/categorySites/'+categoryID,
timeout: deferredAbort.promise
});
// Rather than returning the http-promise object, we want to pipe it
// through another promise so that we can "unwrap" the response
// without letting the http-transport mechansim leak out of the
// service layer.
var promise = request.then(
function( response ) {
return( response.data );
},
function() {
return( $q.reject( 'Something went wrong' ) );
}
);
// Now that we have the promise that we're going to return to the
// calling context, let's augment it with the abort method. Since
// the $http service uses a deferred value for the timeout, then
// all we have to do here is resolve the value and AngularJS will
// abort the underlying AJAX request.
promise.abort = function() {
deferredAbort.resolve();
};
// Since we're creating functions and passing them out of scope,
// we're creating object references that may be hard to garbage
// collect. As such, we can perform some clean-up once we know
// that the requests has finished.
promise.finally(
function() {
promise.abort = angular.noop;
deferredAbort = request = promise = null;
}
);
return( promise );
}
// Return the public API.
return({
getSites: getSites
});
});
Then, in my controller (route 'A' from my problem):
var allSitesPromise = AllSites.getSites(categoryID);
$scope.$on('$destroy',function(){
allSitesPromise.abort();
});
allSitesPromise.then(function(allSites){
// do stuff here with the result
}
I wish the factory wasn't so messy, but I'll take what I can get. However, now there's a separate, related issue Here where, though the promise was cancelled, the next actions are still delayed. If you have an answer for that, you can post it there.
There is a similar question with the answer "How to cancel $resource requests".
While it does not address the question exactly it gives all ingredients to cancel resource request when route is switched:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Cancel resource</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular-resource.js"></script>
<script>
angular.module("app", ["ngResource"]).
factory(
"services",
["$resource", function($resource)
{
function resolveAction(resolve)
{
if (this.params)
{
this.timeout = this.params.timeout;
this.params.timeout = null;
}
this.then = null;
resolve(this);
}
return $resource(
"http://md5.jsontest.com/",
{},
{
MD5:
{
method: "GET",
params: { text: null },
then: resolveAction
},
});
}]).
controller(
"Test",
["services", "$q", "$timeout", function(services, $q, $timeout)
{
this.value = "Sample text";
this.requestTimeout = 100;
this.call = function()
{
var self = this;
self.result = services.MD5(
{
text: self.value,
timeout: $q(function(resolve)
{
$timeout(resolve, self.requestTimeout);
})
});
}
}]);
</script>
</head>
<body ng-app="app" ng-controller="Test as test">
<label>Text: <input type="text" ng-model="test.value" /></label><br/>
<label>Timeout: <input type="text" ng-model="test.requestTimeout" /></label><br/>
<input type="button" value="call" ng-click="test.call()"/>
<div ng-bind="test.result.md5"></div>
</body>
</html>
How it works
$resource merges action definition, request params and data to build a config parameter for an $http request.
a config parameter passed into an $http request is treated as a promise like object, so it may contain then function to initialize config.
action's then function may pass timeout promise from params into the config.
Please look at "Cancel Angularjs resource request" for details.
Take a look at this post
You could do what he is doing and resolve the promise to abort the request on a route change (or state change if using ui router).
It may not be the easiest thing to make happen but seems like it can work.
I cancel the promise with $q.reject(). I think that this way is more simple:
In SitesServices.js:
;(() => {
app.services('SitesServices', sitesServices)
sitesServices.$inject = ['$http', '$q']
function sitesServices($http, $q) {
var sitesPromise = $q.defer()
this.getSites = () => {
var url = 'api/sites'
sitesPromise.reject()
sitesPromise = $q.defer()
$http.get(url)
.success(sitesPromise.resolve)
.error(sitesPromise.reject)
return sitesPromise.promise
}
}
})()
In SitesController.js:
;(() => {
app.controller('SitesController', sitesControler)
sitesControler.$inject = ['$scope', 'SitesServices']
function sitesControler($scope, SitesServices) {
$scope.sites = []
$scope.getSites = () => {
SitesServices.getSites().then(sites => {
$scope.sites = sites
})
}
}
})()
Checking the docs for $resource I found a link to this little beauty.
https://docs.angularjs.org/api/ng/service/$http#usage
timeout – {number|Promise} – timeout in milliseconds, or promise that
should abort the request when resolved.
I've used it with some success. It go a little something like this.
export default function MyService($q, $http) {
"ngInject";
var service = {
getStuff: getStuff,
};
let _cancelGetStuff = angular.noop;
return service;
function getStuff(args) {
_cancelGetStuff(); // cancel any previous request that might be ongoing.
let canceller = $q( resolve => { _cancelGetStuff = resolve; });
return $http({
method: "GET",
url: <MYURL>
params: args,
timeout: canceller
}).then(successCB, errorCB);
function successCB (response) {
return response.data;
}
function errorCB (error) {
return $q.reject(error.data);
}
}
}
Keep in mind
This assumes you only want the results from the last request
The canceled requests still call successCB but the response is undefined.
It may also call errorCB, the error.status will be -1 just like if the request timed out.

Cancelling a request with a $http interceptor?

I'm trying to figure out if it is possible to use a $http interceptor to cancel a request before it even happens.
There is a button that triggers a request but if the user double-clicks it I do not want the same request to get triggered twice.
Now, I realize that there's several ways to solve this, and we do already have a working solution where we wrap $http in a service that keeps track of requests that are currently pending and simply ignores new requests with the same method, url and data.
Basically this is the behaviour I am trying to do with an interceptor:
factory('httpService', ['$http', function($http) {
var pendingCalls = {};
var createKey = function(url, data, method) {
return method + url + JSON.stringify(data);
};
var send = function(url, data, method) {
var key = createKey(url, data, method);
if (pendingCalls[key]) {
return pendingCalls[key];
}
var promise = $http({
method: method,
url: url,
data: data
});
pendingCalls[key] = promise;
promise.finally(function() {
delete pendingCalls[key];
});
return promise;
};
return {
post: function(url, data) {
return send(url, data, 'POST');
}
}
}])
When I look at the API for $http interceptors it does not seem to be a way to achieve this. I have access to the config object but that's about it.
Am I attempting to step outside the boundaries of what interceptors can be used for here or is there a way to do it?
according to $http documentation, you can return your own config from request interceptor.
try something like this:
config(function($httpProvider) {
var cache = {};
$httpProvider.interceptors.push(function() {
return {
response : function(config) {
var key = createKey(config);
var cached = cache[key];
return cached ? cached : cached[key];
}
}
});
}
Very old question, but I'll give a shot to handle this situation.
If I understood correctly, you are trying to:
1 - Start a request and register something to refer back to it;
2 - If another request takes place, to the same endpoint, you want to retrieve that first reference and drop the request in it.
This might be handled by a request timeout in the $http config object. On the interceptor, you can verify it there's one registered on the current request, if not, you can setup one, keep a reference to it and handle if afterwards:
function DropoutInterceptor($injector) {
var $q = $q || $injector.get('$q');
var dropouts = {};
return {
'request': function(config) {
// I'm using the request's URL here to make
// this reference, but this can be bad for
// some situations.
if (dropouts.hasOwnProperty(config.url)) {
// Drop the request
dropouts[config.url].resolve();
}
dropouts[config.url] = $q.defer();
// If the request already have one timeout
// defined, keep it, othwerwise, set up ours.
config.timeout = config.timeout || dropouts[config.url];
return config;
},
'requestError': function(reason) {
delete dropouts[reason.config.url];
return $q.reject(reason);
},
'response': function(response) {
delete dropouts[response.config.url];
return response;
},
'responseError': function(reason) {
delete dropouts[reason.config.url];
return $q.reject(reason);
}
};
}

Resources