I have a fairly large AngularJS application and for logging purposes, I am being tasked with adding a custom header to all of our HTTP requests from the app that contain a unique ID for each request. This really is more valuable to our API calls, but as of now I'm just aiming for all requests (getting templates, styles, etc.)
I am currently using a provider decorator to patch each of the methods exposed by $HttpProvider (implementation based on this post), to attempt to call the ID method each time one of those $http methods runs, and add the appropriate header:
module.config([
'$provide',
function ($provide) {
$provide.decorator('$http', [
'$delegate',
function addUniqueIdHeader($http) {
var httpMethods = ['get', 'post', 'put', 'patch', 'delete'];
/**
* Patched HTTP factory function that adds a request ID each time it is called.
* #param {string} method - A valid HTTP method.
* #return {function} A function that sets various request properties.
*/
function httpWithHeader(method) {
return function(url, data, config) {
config = config || {};
config.headers = config.headers || {};
// the magic
config.headers['My-Custom-Header'] = aUniqueId();
data = data || {};
config.method = method.toUpperCase();
// return `$http` with a modified config, adding the URL and data passed in
// `_.extend()` is lodash, not underscore.
return $http(_.extend(config, {
url: url,
data: data
}));
}
};
// back up the orginal methods and patch
_.each(httpMethods, function (httpMethod) {
var backupMethod = '_' + httpMethod;
$http[backupMethod] = $http[httpMethod];
$http[httpMethod] = httpWithHeader(httpMethod);
});
return $http;
}
]);
}
]);
What I have so far works some of the time, but doesn't seem to work consistently (some API requests have it, some don't). I should note that we are using a quite old version of AngularJS (1.0.6) and no, I cannot upgrade (as much as I would love to) so the use of request interceptors is not possible. Additionally, we use Restangular for the majority of our API interactions.
My question is, is using a provider decorator the right way to go? If so, is there a cleaner way to add the header without having to override/patch each individual HTTP method that I'm overlooking?
Thanks in advance.
I ended up solving my issue by utilizing Restangular's request interceptors, since the version of Angular we use doesn't have them baked in.
Related
I want to send an api key for every request I make:
function MyService($http) {
var req = {
method: 'GET',
url: 'https://api.giphy.com/v1/stickers/trending',
headers: {
'api_key':'123'
}
}
return $http(req);
}
but the problem is that all requests are OPTIONS (not GET) and is not sending the api_key. Is that the right way to send headers? thanks
Editing because it was marked as duplicate:
This is not a CORS issue. The error I´m getting is 401. That means authentication failed because the endpoint is not receiving the request header with the api_key.
What you did is totally fine, but if the api_key is always different, so you have to provide the api_key value dynamically in order to be added to the request.
If it is always the same, you have a really better way to do that: through interceptors. And you will set that only one time. Again, this method is if you have to set up some parameter which is always the same, so actually it is for doing standard operations over HTTP requests.
First, you need to define your Interceptor:
myApp.service('MyRequestsInterceptor', [function() {
this.request = function(config) {
config.headers.api_key = 'My Default API KEY';
return config;
};
}]);
And then simply add your interceptor to AngularJS $httpProvided:
myApp.config([ '$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('MyRequestsInterceptor');
} ]);
Is there a way to change the query string of JavaScript-induced requests? I want to add "&myParam=myValue" to any request sent by my HTML/JS application.
I don't think there's anything built in that lets you do that.
In my apps, I always have a central function XHR goes through so I have a single point to do things like this. If you don't have that or need to intercept calls from 3rd party libs:
You could wrap XMLHttpRequest.open to handle the XHR ones:
var originalOpen = XMLHttpRequest.open;
XMLHttpRequest.open = function() {
var args = Array.prototype.slice.call(arguments);
args[0] += (args[0].indexOf("?") == -1 ? "?" : "&") + "myParam=" + encodeURIComponent("myValue");
return originalOpen.apply(this, args);
};
...and then similar for fetch. But it seems brittle.
Alternately, you might look at using a cookie for the parameter, as the browser will add the cookie to the requests. (That assumes the requests are going to an origina you can add cookies for in your code.)
You could use partial application to lock in defaults when you declare your fetch function and essentially decorate the standard call that will merge your defaults and the passed params.
const fetchFactory = defaults => (url, data) => {
// make a copy of the defaults
const params = Object.assign({}, defaults)
// assign the passed in data with the defaults
params.body = JSON.stringify(Object.assign(params.body, data))
// call fetch with the params
return fetch(url, params)
}
// create a default for POST using the factory
const postFetch = fetchFactory({
method: 'post',
headers: {
'x-requested-with': 'fetch',
'Authorization': 'basic:' + btoa('a secret')
},
body: {
myParam: 'value'
}
})
// now you can call your function
postFetch('http://somewhere.com', {
one: 1,
two: 2
})
.then(respone => response.json())
It seems to me that you are asking how to set/edit URL parameters in http requests. Something quite similar has been asked here: here
If you are using XMLHttpRequest then the accepted answer in the link should work perfectly. The two key things the note are
the url parameters are simply a javascript object that you convert
into a JSON string. This happens through JSON.stringify({ myParam:
'hi'});
the question/answer linked are making post requests but you
may not want to make that request as well, I suggest doing some
research about which HTTP request method you want -
https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
This question already has answers here:
What is the correct way to share the result of an Angular Http network call in RxJs 5?
(22 answers)
Closed 6 years ago.
I have a page which makes http requests to the same location, just with different parameters depending on what the user wants. So my code looks something like this:
this.http.post( //I am open to POST methods or GET methods as I have security in the back to prevent malicious writes.
'http://192.168.1.45:3000/mylocation',
'p1=' + param1 + '&p2=' + param2,
{headers: headers}
)
In JQuery for example you have build into the framework a cache attribute which caches automatically and is very easy to implement:
$.ajax({
cache: true,
//other options...
});
Does Angular2 have something similar to this? I would like to cache these dynamic responses as long as the user is in the application. So if a user requests the same url with the same parameters then it would just grab it from the cache, and if the params were never yet used then it would make the network call.
I could not find anything in the Angular2 docs in the request options:
https://angular.io/docs/js/latest/api/http/RequestOptions-class.html
Cache the data, and if cached data is available return this, otherwise make the HTTP request. If you want to reuse for different requests (parameters) you can adjust to store the references in an array instead.
getData() {
if(this.data) {
// if `data` is available just return it as `Observable`
return Observable.of(this.data);
else if(this.observable) {
// if `this.observable` is set then the request is in progress
// return the `Observable` for the ongoing request
return this.observable;
} else {
// create the request, store the `Observable` for subsequent subscribers
this.observable = this.http.get('/someUrl')
.map(res => res.json())
.do(val => {
this.data = val;
// when the cached data is available we don't need the `Observable` reference anymore
this.observable = null;
})
// make it shared so more than one subscriber can get the result
.share();
return this.observable;
}
}
I'm trying to get list of tracks from soundcloud API with angularjs.
The parameters i'm trying to send are:
1) client_id (string)
2) duration (object with two properties).
Here's the code:
var CLIENT_ID = 'a81f01ef5d0036415431e8be76c8db0e';
var TRACKS_URL = 'https://api.soundcloud.com/tracks.json';
var app = angular.module('soundcloud', []);
app.controller('tracksController', function ($scope, $http) {
$http({
url: 'https://api.soundcloud.com/tracks.json',
method: 'GET',
data: {
client_id: CLIENT_ID,
duration: { // in milliseconds
from: 300000,
to: 400000
}
}
})
.success(function (data) {
$scope.trackList = data;
})
.error(function () { alert('error'); });
});
These parameters aren't recognized at all when I check the request in the broweser's debugger.
I tried to use 'params' instead of 'data', but this way it turns the 'duration' object to json --> then I get status 500 in response.
When I only send the client_id in params, it works fine because there's no object, only string.
jQuery's ajax method works fine: https://jsfiddle.net/oacwz1up/3/
What should I do ? How can I send the parameters normally ?
Help please! Thanks!
This happens because Angular serializes the request parameters differently than JQuery.
Angular 1.4 will address this problem introducing a paramSerializer property on the $http config object (and on $httpProvider.defaults). This can be used for arbitrarily serializing the requests parameters (for all or a particular request).
Note, that the feature is available only since v1.4.0-rc.0.
Besides the default serializer (which converts objects to JSON strings), there is an additional built-in serializer that emulates JQuery's param(): $httpParamSerializerJQLike.
You can use it like this:
$http.get(<API_URL>, {
params: {...},
paramSerializer: '$httpParamSerializerJQLike'
});
See, also, this short demo.
If you are using an older version of Angular, you could do one of the following:
Construct the whole URL (including the query string) yourself (possibly using an $http request interceptor to automatically apply this to all requests).
Keep the params in a flat format that will result in Angular's serializing them as expected:
var REQ_PARAMS = {
client_id: 'a81f01ef5d0036415431e8be76c8db0e',
'duration[from]': 200000,
'duration[to]': 205000
};
If you look at $http documentation, on request it applies a default transformation which is $http.defaults.transformRequest and as described will do the following (as you saw) :
If the data property of the request configuration object contains an object, serialize it into JSON format.
What you need to do is override this function by specifying your own transformRequest object.
function appendTransform(defaults, transform) {
// We can't guarantee that the default transformation is an array
defaults = angular.isArray(defaults) ? defaults : [defaults];
// Append the new transformation to the defaults
return defaults.concat(transform);
}
$http({
url: '...',
method: 'GET',
transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
return doTransform(value);
})
});
You need to find a way to get the same syntax as jQuery provide which is :
https://api.soundcloud.com/tracks.json?client_id=a81f01ef5d0036415431e8be76c8db0e&duration[from]=200000&duration[to]=205000
Use a condition is is an object and generate manually your String. Should not be difficult.
that's a strange API - I don't know why they don't do something like "duration_from" rather than requiring duration[from] - as suggested you could certainly transform the request, but if this is just a one off you could also try simply hard-coding it using url escaped values for [ and ]:
var dataToSend = {
client_id: 'a81f01ef5d0036415431e8be76c8db0e',
'duration%5Bfrom%5D': 200000,
'duration%5Bto%5D': 205000
};
$http.get('http://api.soundcloud.com/tracks.json', {params:dataToSend});
The params property is way to transform query in restful way. Just transform data in dataToSend object, and it will work.
This is a URL that should be created:
https://api.soundcloud.com/tracks.json?client_id=a81f01ef5d0036415431e8be76c8db0e&duration%5Bfrom%5D=200000&duration%5Bto%5D=205000
In my REST server, it's requiring the access_token to be present in every request. i.e. in POSTing data, access_token needs to be submitted together with the attributes.
How do I configure backbone.js to add access_token to every GET, PUT, POST and DELETE request?
Thanks.
Okay, I think I found a way how to do it in jQuery.
$.ajaxSetup (
{
data: { access_token: 'my_access_token' }
}
);
Backbone uses jQuery/Zepto for AJAX requests, so you can use the functionality available in those libraries.
To add custom headers to all XHR calls made by jQuery, you can use the jQuery.ajaxSend event, which is triggered before every ajax request, and modify the jqXHR it receives as an argument.
Edit based on OP's comments:
Probably the simplest way to modify the sent data is to override the Backbone.sync function. You could wrap the native implementation, and add the required property there:
var nativeSync = Backbone.sync;
Backbone.sync = function (method, model, options) {
//for POST/PUT requests, add access token to the request
if(model && (method === 'create' || method === 'update')) {
var data = _.extend(model.toJSON(), {
access_token: 'token'
});
options.data = JSON.stringify(data);
}
//call the native Backbone.sync implementation
nativeSync(method, model, options);
};