Reputation: 166
I have an angular provider for querying an OData service.
Now I'm trying to build a $filter function onto that provider, so I can use it throughout my app.
The problem I'm running into, and so far haven't been able to solve, is that the query part of the URL needs to start with '$filter=' which I can handle fine when there is one filter, but multiple filters are added as ' and {query goes here}'.
A sample query would be:
http://www.example.com/odata/Restaurants/?$filter=id eq 2 and city eq 'Los Angeles' and substringof("Lagasse", chef)&$top=20
I'm sending all the filters in an array to the provider. Ideally, I would use "$filter=#{firstFilter}" for the first filter in the array and for the remaining filters use " and #{remainingFilter}" but I'm unsure how to structure this code.
My current code uses multiple if statements to check if a filter is there, but with the nature of building the url, it makes one of the filters mandatory at all times. I'd like to avoid this.
For example:
var filter = "$filter=id eq 2";
if (city) {
filter += " and city eq #{cityName}";
}
if (chef) {
filter += " and substringof(#{chefName}, chef)";
}
Now everytime a user inputs a query, they have to specify an id.
We are NOT using BreezeJS, JayData, or any other library. Strictly AngularJS and specifically $http, NOT $resource.
Upvotes: 3
Views: 13156
Reputation: 1672
You can use odata-filter-builder to build $filter part for OData URL query options.
Then just use $http
with config params.
Short example:
var filter = ODataFilterBuilder()
.eq('id', 2)
.eq('city', 'Los Angeles')
.and('substringof("Lagasse", chef)')
.toString();
$http
.get(resourceUrl, {params: {$filter: filter, $top: 20}})
.then(function(response) {
// Handle response
})
Full example:
angular
.module('OData', [])
.constant('ODataFilterBuilder', ODataFilterBuilder)
.factory('ODataService', function($http) {
return {
load: function(resourceUrl, queryParams) {
return $http.get(resourceUrl, {params: queryParams})
.then(function(response) {
return response.data.value;
});
}
}
});
angular
.module('app', ['OData'])
.controller('appController', function($http, ODataService, ODataFilterBuilder, $httpParamSerializer) {
// 1. inject ODataFilterBuilder
// use short name for filter builder
var f = ODataFilterBuilder;
// 2. build filter
var filter = f()
.eq('id', 2)
.eq('city', 'Los Angeles')
.and('substringof("Lagasse", chef)')
// 3. creater odata query params
var queryParams = {
$filter: filter.toString(),
$top: 20
// TODO: add other params
};
// 4. prepare odata resourse URL
var odataServiceUrl = 'http://path/to/odata/service/';
var odataResourseUrl = odataServiceUrl + 'entity';
// 5. Do http request with odataResourseUrl and queryParams
// use ODataService or angular $http service
// ODataService
// .load(odataResourseUrl, queryParams)
// .then(function(value) {
// // handle value
// });
// OR
// $http.get(odataResourseUrl, {params: queryParams})
// .then(function(respons) {
// // handle respons.data.value
// });
// Result examles:
// NOTE: $httpParamSerializer - default $http params serializer that converts objects to strings
var queryParamsSerialised = $httpParamSerializer(queryParams);
// put values to 'this' to use it in html
this.queryParams = queryParams;
this.queryParamsSerialised = queryParamsSerialised;
this.queryUrl = odataResourseUrl + '?' + queryParamsSerialised;
});
<div ng-app="app" ng-controller="appController as vm">
<pre>queryParams: {{vm.queryParams|json}}</pre>
<pre>queryParamsSerialised: {{vm.queryParamsSerialised}}</pre>
<pre>queryUrl: {{vm.queryUrl}}</pre>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js"></script>
<script src="https://npmcdn.com/odata-filter-builder@^0.1/dist/odata-filter-builder.js"></script>
Upvotes: 7
Reputation: 3131
I've done similar and have what I think is an elegant solution with no dependencies on other libraries. I use Array.join and concatenate the filters with an "and" between them:
var filter = [];
if (city) filter.push("City eq '" + city + "'");
if (chef) filter.push("Chef eq '" + chef + "'");
var filterQuery = filter.join(" and ");
This assumes you always want "and" between them, (not or), but that is the case in my apps and in your example. I also do this to build up the url:
var parts = [baseurl];
parts.push("$top=10");
parts.push("$skip=" + skip);
parts.push(filterQuery);
var url = parts.join("&");
Upvotes: 1
Reputation: 269
I know you said you are not using any other library, but in case anyone else stumbles up this...I suggest using joData. It's very expressive and requires no other dependencies: https://github.com/mccow002/joData
Here is your example in joData:
var query = new jo('http://www.example.com/odata/Restaurants');
query.filter(new jo.FilterClause('id').eq(2))
.andFilter(new jo.FilterClause('city').eq('Los Angeles'))
.andFilter(new jo.FilterClause('chef').substringof('Lagasse').eq(true))
.top(2);
Then,
query.toString()
produces...
"http://www.example.com/odata/Restaurants?$top=2&$filter=id eq 2 and city eq 'Los Angeles' and substringof('Lagasse',chef) eq true"
Anyway, to set this up for Angular here is what I did...
Reference joData js in your html before your angular.js reference.
Wrap it in an Angular service
angular.module('app.shared.oData', []) .service('oDataBuilderService', function () { this.jo = jo; };
Now other services can use this odata service to construct odata queries:
angular.module('app.shared.video', []) .service('videoService', function ($http, oDataBuilderService) {
this.getByVideoCatalogId = function (videoCatalogId) {
var query = new oDataBuilderService.jo('http://www.example.com/VideoCatalog/OData');
var videoCatalogIdEquals = new oDataBuilderService.jo.FilterClause("VideoCatalogId").eq(videoCatalogId);
query.andFilter(videoCatalogIdEquals);
var queryAsString = query.toString();
var promise = $http.get(queryAsString);
return promise;
}
});
Upvotes: 2
Reputation: 6553
You can add $filter=
at the end, but then you'd have the same problem with the "and "
Easier would be to just create a simple function
addFilter(currentFilter, filter) {
if (currentFilter.length == 0) {
currentFilter = "$filter=" + filter;
} else {
currentFilter += "and " + filter;
}
return currentFilter;
}
so then just call currentFilter = addFilter(currentFilter, "what to filter")
var currentFilter = "";
if (city) {
currentFilter = addFilter(currentFilter, "city eq #{cityName}");
}
if (chef) {
currentFilter = addFilter(currentFilter, "substringof(#{chefName}, chef)");
}
Upvotes: 1