angular-resource.js encodeUriSegment Issue - angularjs

I have an issue querying restful resources when the resource uri has several subpaths :
Example :
.factory('SomeFactory', function ($resource) {
return $resource('/path/subPath/otherSubPath/:id', {}, {
show: { method: 'GET', params: { id: '#id' } }
})
}) ;
When I invoke SomeFactory.show in a controller I get the error
the server responded with a status of 400 (Bad Request)
This is because the browser is looking for the uri :
http://server/path/subPath%2FotherSubPat/id
Notice the %2F replacing the / (slash) in the uri , I have tried many tricks in javascript to make this work ; But the only solution was to add the following last line replace(/%2F/gi, '/'); in angular-resource.js encodeUriSegment method .
Please tell me if this approach is correct .
function encodeUriSegment(val) {
return encodeUriQuery(val, true).
replace(/%26/gi, '&').
replace(/%3D/gi, '=').
replace(/%2B/gi, '+').
replace(/%2F/gi, '/');
}
Thanks .

it seems like you have already bumped into this issue opened on github:
https://github.com/angular/angular.js/issues/1388#issue-6979382
where the approach you've taken is suggested. As far as I am concerned, as I had the same issue as you, I preferred to have multiple services (one for each subpath) rather than modifying angular-resource.js - I prefer to not modify core lib files as any updates will wipe those changes.
Hopefully a flag to se the uri encoding will be added to Angular to solve this problem.

You can use http interceptor:
module.constant("interceptULRS",
[new RegExp('.+/path/.*')]);
module.config(['$httpProvider', 'interceptULRS', function($httpProvider, interceptULRS) {
var ENCODED_SLASH = new RegExp("%2F", 'g');
$httpProvider.interceptors.push(function ($q) {
return {
'request': function (config) {
var url = config.url;
for (var i = 0; i < interceptULRS.length; i++) {
var regex = interceptULRS[i];
if (url.match(regex)) {
url = url.replace(ENCODED_SLASH, "/");
// end there is only one matching url
break;
}
}
config.url = url;
return config || $q.when(config);
}
};
});
}]);
Keep in mind that it will intercept all URLs.

Building off of #Pavol I think you can pare the request function down to
var ENCODED_SLASH = new RegExp("%2F", 'g');
var request = function (config) {
var matches = config.url.match(ENCODED_SLASH);
if (matches && matches.length) {
config.url = config.url.replace(ENCODED_SLASH, "/");
}
return config || $q.when(config);
};
if you don't need ENCODED_SLASH elsewhere, and don't care where the "%2F"s are in the url.
As I work more and more with the angular-resource I'm finding using and understanding the $httpProvider.interceptors to be more important.

Related

Dynamic URL queries with AngularJS and NodeJS, MongoDB

I really do not understand how to handle URLs with queries appended to it.
I have endpoints that accept several parameters like:
?max_length=50,
?min_length=1,
?active=true,
?only_today=true,
?etc...
Via AngularJS how can I set those value dynamically only if the user has checked for those values?
Actually I'm just building an object {} appending those parameters when the $scope is not null. But I don't think it is a good idea.
Same for NodeJS and MongoDB...
How can I get the correct object based on the query string on the URL?
What I'm doing here as well is to split up the URL and checking for the words, etc... I'm sure there is a better way and I can not find it in both documentations and wondering to bigger and bigger URL parameters it start to be hell.
I know this is a real low level question but I don't really understand how to handle it.
Thanks
You can use the $location service for that.
https://docs.angularjs.org/api/ng/service/$location
You can use $resource to easily map your endPoints to your services. You should map your params to what is expected in your api. And if you have conditional parameters, you need to handle undefined params in your backend and ignore these params. For mapping your endpoints in nodeJS check out Restify
For example:
angular.module("myApp", []).factory("myFactory", function($resource) {
var YourResource = $resource('/rest/yourResource');
var factory = {
retriveMyResources: function(paramsQuery) {
return YourResource.query(paramsQuery).$promise;
}
};
return factory;
}).controller("myCtrl", function($scope, myFactory) {
myFactory.retrieveMyResources({
maxLength: $scope.maxLength,
sortBy: $scope.sortBy
}).then(function(result) {
$scope.result = result
})
})
Your node server
//App.js you initialize your server, and include your route files
(function() {
var restify = require("restify");
var server = restify.createServer({
name: "test-server"
});
server.pre(restify.CORS());
server.use(restify.gzipResponse());
server.use(restify.acceptParser(server.acceptable));
server.use(restify.queryParser());
server.use(restify.bodyParser());
server.use(restify.jsonp());
require("./routes/your_resource_route.js")(server);
server.listen("1921", function() {
console.log('%s listening at %s environment: %s', server.name, server.url, process.env.NODE_ENV);
});
})();
Example Route file
module.exports = function(server) {
var yourResourceService = require("services/your_resource_service.js");
server.get("rest/yourResource",
function(req, res, next) {
return yourResourceService.findResources(req.params.maxLength, req.params.sortBy).then(function(resources) {
res.send(200, resources);
next();
}).catch(function(err) {
res.send(500, err);
next();
}).done();
}
);
}
And your service file
module.exports = function(app_context) {
var exampleService = {
findItems: function(maxLength, sortBy) {
var sortObject = {};
sortObject[sortBy || DEFAULT_SORT] = -1;
return Q(brandMongooseCollection.find({}).sort(sortObject).limit(maxLength || DEFAULT_MAX_LENGTH).lean().exec());
}
};
return exampleService;
};

Can't get only the header in AngularJS (v1.3.15) using $resource and method 'HEAD'

In Angular (v1.2.19), I was able to do something like this in a factory :
myApp.factory('GetNumber', ['$resource',
function($resource) {
var get_headers = $resource('some/url', null, {
get_number: {
method: 'HEAD',
transformResponse: function(data, headers) {
var count = headers()['x-number'];
return count;
}
}
});
return get_headers;
}
]);
Call it from my controller like this:
$q.all({
'item1': GetNumber.get_number().$promise,
'item2': SomeOtherService.get().$promise
})
.then(function(results) {
$scope.newNumber = results.item1.value;
});
And I could get the custom header back without having to retrieve the whole header.
Now in v1.3.15, it doesn't work. I can see the header in Chrome with 'x-number' in the header, but if I put a breakpoint in Chrome on the 'var count' line, I never hit it (and I do hit it with v1.2.19).
I've verified that using $http.head works, so if I have this in my controller:
$http.head('some/url')
.success(function(data, status, headers, config) {
var count = headers()['x-number'];
$scope.newNumber = count ? count : 0;
});
I get my scoped value.
I've noticed that there aren't a whole lot of examples of people using the http 'HEAD' method and I'm wondering if there's a reason that I haven't located yet through searching?
I did find this StackOverflow question and answer HTTP Get: Only download the header? (HEAD is not supported) and while I agree with the statement, I don't want the overhead of requesting the headers and the body.
Any suggestions please?
Julie
Thank you to Kevin for suggesting that I use an error handler. I should've thought to try that myself, but I didn't.
Anyway, that lead me to the answer to my problem. To try and catch an error in $resource, it's suggested you use interceptors. I've never used them before and I utilized AngularJS documentation (https://docs.angularjs.org/api/ng/service/$http#interceptors) and changed the code in my factory to be:
myApp.factory('GetNumber', ['$resource',
function($resource) {
var get_headers = $resource('some/url', null, {
get_number: {
method: 'HEAD',
interceptor: { response: function(response) {
var count = response.headers()['x-number']:
return count ? count : 0;
}, responseError: function(rejection) {
console.log('rejection: ', rejection);
}}
}
});
return get_headers;
}
]);
I'm still not sure why transformResponse stopped working and I now need to use interceptor, but very happy I don't have to request the whole body now!
Julie

Angular.js prepends base path to absolute path URL

I'm absolutely new to angular.js, so I'm sorry for a such stupid question. But maybe it would help a little bit to other beginers.
I'm learning from a sample project created by someone else, but I'm using a local IIS (instead of IIS Express or VS dev. server). So my base path is http://localhost/SampleProject
The sample project contains this $http.get
$http.get('https://maps.googleapis.com/maps/api/geocode/json',
{
params: params,
headers: { 'Accept-Language': 'en' }
}
)
This works on base URL like "http://localhost:1234", but in my case I get
Failed to load resource: the server responded with a status of 400 (Bad Request)
because request URL is
http://localhost/SampleProject/https://maps.googleapis.com/maps/api/geocode/json?sensor=false
Can anyone tell me why is angular.js prepending base url even if absolute path URL is used?
Thanks,
Tom
Nothing related to $http
From 1.3, Angular does not allow global variable controller.
Here is the working version:
http://plnkr.co/edit/zGj4Ayy8NIplUva86NP6?p=preview
var app = angular.module('myApp', []);
app.controller('customersController',
function($scope,$http) {
var params = { address: "Zlin, Czech Republic", sensor: false };
$http.get("https://maps.googleapis.com/maps/api/geocode/json", {
params: params,
headers: { 'Accept-Language': 'en' }
}).success(function(response) {
$scope.names = response;
});
});
I've solved this issue. It was caused by wrong default URL handling.
I had to add
if (config.url.indexOf('http') !== 0 && config.url.indexOf('//' !== 0))
line into app.js file:
app.config(["$httpProvider", function ($httpProvider) {
$httpProvider.interceptors.push('middleware');
}]);
app.factory('middleware', ['__baseUrl',
function (__baseUrl) {
return {
request: function (config) {
// handle absolute URL request
if (config.url.indexOf('http') !== 0 && config.url.indexOf('//' !== 0))
{
config.url = __baseUrl + config.url
}
return config;
}
};
}]);

Set different $http url prefix for template and server requests

Basically what I'm trying to do is to set a prefix for all $http server requests (the server url). I tried to use an interceptor, but the problem is that this also affects the template requests:
$httpProvider.interceptors.push(function ($q) {
return {
'request': function (request) {
request.url = "http://localhost/"+request.url;
return request || $q.when(request);
}
}
});
=>
XMLHttpRequest cannot load http://localhost/templates/main.html
I thought about using my own provider (for example $myHttp) which inherit $http, but i don't know how to do this.
So what is a good solution for this?
If all of your templates are in the templates directory, you could just ignore those in your interceptor.
$httpProvider.interceptors.push(function ($q) {
return {
request: function (request) {
if (request.url.indexOf('templates') === -1) {
request.url = "http://localhost/" + request.url;
}
return request || $q.when(request);
}
}
});

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