AngularJS: Prevent calling multiple $http request to get same data - ajax

I still don't understand how the Promise API works. I want to know if there's a way to get a data whenever I need it without calling multiple HTTP request. Here's an exemple :
Session Service :
All it does is either get the session object (which contains datas) or get session ID which returns a number.
app.factory('sessionFactory', ['$resource', 'requestFactory',
function ($resource, requestFactory) {
var oSession = {};
var session = {
/**
* Get session ID
* #return {Number}
*/
sessionID: function () {
if (typeof oSession.id !== "undefined") {
return oSession.id;
} else {
return requestFactory.getObject('/application/current_session').then(function (response) {
oSession = response;
return oSession.id;
});
}
},
/**
* Get session object (GET)
* #return {Object} data in JSON format
*/
getCurrentSession: function () {
if (!oSession.id) {
return requestFactory.getObject('/application/current_session').then(function (response) {
oSession = response;
return oSession;
});
}
}
};
return session;
}]);
Request HTTP Service :
This service only does HTTP request.
app.factory('requestFactory', ['$http', '$q', '$timeout',
function ($http, $q, $timeout) {
return {
getObject: function (jsonURL, params) {
// $q service object
var deferred = $q.defer();
// regular ajax request
$http({
method: 'GET',
url: jsonURL,
params: params
})
.success(function (result, status, headers, config) {
// promise resolve
deferred.resolve(result);
})
.error(function (result, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
deferred.reject('Erreur request : ' + status);
});
return deferred.promise;
}
};
}]);
So to get my Session Object, I do sessionFactory.getCurrentSession with callback function(then...) and it works perfect. Later on, I only need to get the session ID so I would do sessionFactory.sessionID, but it works only if I add the callback function (then...), why is that? I thought my global JavaScript object oSession already has data since the first HTTP request.
I want to prevent doing a spaghetti code and keep the code as clean as possible, with a more object approach. Is it possible?

It looks like you're trying to do too much with the promise API. It's already built into the $http service so you shouldn't need to invoke it yourself. Try this instead:
app.factory('requestFactory', ['$http',
function ($http) {
return {
getObject: function (jsonURL, params) {
// regular ajax request
return $http({
method: 'GET',
url: jsonURL,
params: params
})
.success(function (result, status, headers, config) {
return result;
})
.error(function (result, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
throw new Error('Erreur request : ' + status);
});
}
};
}]);
By returning the result of the $http call, you are in fact returning a promise. You can then chain additional resolution code onto the return value. See the "Chaining Promises" section of the $q documentation.

IF you want to cache your previous http response so that it will not make an http call again
you can use angular-cached-resource
bower install angular-cached-resource
angular.module('myApp',['ngCachedResource'])
instead of $resource use $cachedResource, it will cache the network call to local storage, every time you make a call it will resolve immediately even though it makes a call to backend and updated the cache.
you can also use angular-cache it will cache all your http get calls you can set timeout as well exclude url in its configuration
bower install angular-cache

Related

AngularJS simple auth interceptor

I want to pass in the headers my token each time i make a request. the way i do it now is using:
$http.defaults.headers.common['auth_token'] = $localStorage.token;
How could i do that to make that sent to every request, and when it throws an error it should do a
$state.go('login')
If you want to add your token to each request, and respond to any errors, your best bet would be to use an Angular HTTP interceptor.
Subject to your needs, it might look something like this:
$httpProvider.interceptors.push(function ($q, $state, $localStorage) {
return {
// Add an interceptor for requests.
'request': function (config) {
config.headers = config.headers || {}; // Default to an empty object if no headers are set.
// Set the header if the token is stored.
if($localStorage.token) {
config.headers.common['auth_token'] = $localStorage.token;
}
return config;
},
// Add an interceptor for any responses that error.
'responseError': function(response) {
// Check if the error is auth-related.
if(response.status === 401 || response.status === 403) {
$state.go('login');
}
return $q.reject(response);
}
};
});
Hope this helps.

Get scope which the request is originated in interceptor

In AngularJS response error interceptors, is there any way to retrieve the scope which the current request is originated?
module.controller('myController', function($http, $scope) {
$scope.getData = function() {
// This scope is which the request is originated
$http.get('http://www.example.com/get', {
params: { 'id': '1234' }
}).success(function(data, status, headers, config) {
// ...
}).error(function(data, status, headers, config) {
// ...
});
}
});
module.factory('myInterceptor', function($q) {
return {
'responseError': function(response) {
// How can I get the scope of 'myController' here?
return $q.reject(response);
}
};
});
I don't think you can access originating scope from factory out of the box.
Factories are singletons and it is not recommended to play with scope in it.
Nevertheless, I believe you can pass the originating scope as part of param object from your controller.
params: { 'id': '1234', 'origScope': $scope }
update
after following discussion...
I think instead of accessing the scope here.
You publish the event from interceptor and have scope or associated controller listen it.
Refer this link for more information: http://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/

Cannot submit form with supertest and mocha

I am using supertest and mocha to test a nodejs application. One of the things users can do is to submit a very simple form, which is picked up by the node server and parsed using formidable.
Here is the mocha test code:
var should = require('should'),
express = require('express'),
app = require('../app.js'),
request = require('supertest'),
csrfToken,
sessionId,
cookies = [];
describe('Post Handler', function(){
it('Uploads new post', function(done){
var req = request(app).post('/post?_csrf=' + csrfToken);
req.cookies = cookies;
req
.type('form')
.send({fieldTitle: 'autopost'})
.send({fieldContent: 'autocontent'})
.send({contentType: 'image/png'})
.send({blobId: 'icon_23943.png'})
.expect(200)
.end(function(error, res){
console.log('here');
done();
});
});
csrfToken retrieves a csrf token from the server, since I am using the csurf module and every POST method requires a csrf token. cookies stores the session cookie that is provided by the node server so I can persist the session between requests.
The form is processed by the following code:
//Takes HTTP form posted by client and creates a new post in the Db
exports.postPostUpload = function (req, res) {
var form = new formidable.IncomingForm();
form.parse(req, function (err, fields, files) {
console.log(err);
if (err) res.redirect(303, '/error');
else {
var new_post = new post_model.Post().createNewPost(fields);
new_post.setUserId(req.session.passport.user.userId);
new_post.uploadPostToDb(function (error, result) {
if (error) return res.status(500).end();
else {
if (new_post.media.contentType.indexOf('video') !== -1) {
addMessageToEncodingQueue(new_post, function (error, result, response) {
if (error) {
errorHelper.reportError({
stack: new Error().stack,
error: error
});
res.status(500).end();
}
else res.status(200).send(new_post.cuid);
});
}
else return res.status(200).send(new_post.cuid);
}
});
}
});
}
My current problem is, that once the form handler executes the line form.parse(req, function (err, fields, files) {, nothing happens. Formidable does not return error, it just does not return anything. Consequently, the mocha test never receives a reply from the server, and eventually the socket hangs and the test crashes. Needless to say, the form is successfully submit if you do it manually via the website.
There must be an error in the way supertest/mocha are executing this test, but I have not been able to find it. Any pointers are highly appreciated.

Asynchronously populate Sammy.Storage cache

I'm having difficulty accessing requestJSON on a jQuery $.ajax object outside of the success callback. If I do:
var ajax_request = $.ajax({
dataType: 'json',
contentType: 'application/json'
});
console.log(ajax_request.responseJSON);
// this results in `undefined`
How can I access the responseJSON without adding a .success() callback? If I inspect ajax_request in Firebug, I can see the responseJSON property, and the data I expect, but I can't access it via:
ajax_request.responseJSON
More specifically, I'm building an SPA using Sammy and Knockout. In some routes, I need to be able to get JSON from cache, and if it doesn't exist, get the value from a service call and then set it into cache:
var cached_json = storage.fetch('cached_json', function() {
// make service call
return $.getJSON(url);
});
event_context.render('template.tpl', {'json': cached_json}).appendTo('#my-target');
But, of course, calling storage.fetch doesn't cause the rest of the code to pause until $.getJSON is complete. This is the part I can't quite figure out how to structure.
here's how i would implement it
responseJSON = "";
$.get("myurl.php",function(jdata){
responseJSON = jdata;
},"json");
i like to see the ajax method at a glace, but in your case you can do the same by
....
success : function(jdata){ responseJSON = jdata; }
....
PS: i believe that initializing the blank responseJSON is not required since any variable without var is in global scope, but it would help for clarity
I ended up solving this by creating a deferred object that gets or creates the value I need:
function get_or_create_cache(storage, key, service_endpoint) {
return $.Deferred(function(deferred) {
var c = storage.get(key);
if (c === null || c === undefined) {
$.when(jsonp_service_request(service_endpoint)).done(function(json) {
storage.set(key, json);
deferred.resolve(json);
});
}
else {
deferred.resolve(c);
}
}).promise();
}
In this function, storage refers to a Sammy.Storage instance. jsonp_service_request is a local function that returns a jsonp response, taking into account the location.hostname for local development, where I'm pointing to local.json files, or a remote environment, where I'm calling into an actual API. jsonp_service_request returns an $.ajax function.
Then in my Sammy route, I can do:
this.get('#/', function(event_context) {
$.when(get_or_create_cache(storage, 'my-cache-key', 'service-endpoint'))
.then(function(json) {
event_context.render('my-template.template', {'value-name': json})
.appendTo('#my-target');
});
});

JSON Representation of a Backbone Model

Forking a json file using fetch method triggers error in my code. Seems the response from the server is not right. Into the details, for the ads model below
//Advertisement model
App.Tasks.Model.Ads = Backbone.Model.extend({
url: "ads/ads.json",
intialize: function () {
},
Next: function () {
var ads = this.get("ads");
return ads[Math.ceil(Math.random(0, ads.legth) * 10)];
}
});
how should the server response be when calling fetch(). Right now it is as below
{ads: ["1.png", "2.png", "3.png"]}
and doing this triggers the error callback
//Advertisement model
App.Tasks.Ads = new App.Tasks.Model.Ads();
App.Tasks.Ads.fetch({
success: function (model, response) {
console.log("Success", arguments);
},
error: function (model, response) {
console.log("Error", arguments);
}
});
Your server responds with an invalid JSON, the left part in a name/value pair must be a string, which means that ads should be wrapped in double quotes:
{"ads": ["1.png", "2.png", "3.png"]}
For the complete reference, check http://www.json.org/

Resources