The following HTML, Javascript and JSON render correctly, but the filter does not work at all. What are we doing wrong?
Assuming that you'd like to filter in catalogs using distributor name then the following custom filter does the job:
<div data-ng-controller="dashboard_controller">
<h1>
Catalogs
<input type="text" data-ng-model="catalog_filter" placeholder="Filter Distributors">
</h1>
<div class="catalogs_listing">
<ul data-ng-repeat="catalog in catalogs | distributorName: catalog_filter:distributors">
<li> <a href="{{catalog.distributor_id}}/d/products/catalog_view/{{catalog.uid}}">
<div class="catalog_thumb">
<div class="catalog_thumb_image">
<span>demo - no image</span>
</div>
</div>
<div class="catalog_info">
<h2>{{distributors[catalog.distributor_id].name}} <span>{{catalog.products_count}}p</span></h2>
<p>{{catalog.name}}</p>
</div>
</a>
</li>
</ul>
</div>
</div>
app.filter('distributorName', function() {
return function(items, filterValue, distributors) {
if (!filterValue){
return items;
}
var result = {};
angular.forEach(items, function(value, key) {
var distributor = distributors[value.distributor_id];
if (distributor && distributor.name && distributor.name.toLowerCase().indexOf(filterValue.toLowerCase()) > -1){
result[key] = value;
}
});
return result;
};
});
JSFiddle with local data: http://jsfiddle.net/alfrescian/p8zgp/
Very helpful to know ... here's a more generic version of alfrescian's solution, to filter a list of objects by a sub-key.
app.filter('objFilter', function() {
return function(items, filter) {
if (!filter){
return items;
}
var result = {};
angular.forEach( filter, function(filterVal, filterKey) {
angular.forEach(items, function(item, key) {
var fieldVal = item[filterKey];
if (fieldVal && fieldVal.toLowerCase().indexOf(filterVal.toLowerCase()) > -1){
result[key] = item;
}
});
});
return result;
};
});
You can then have multiple filter boxes, like this
<input ng:model="filter.firstName"/>
<input ng:model="filter.lastName"/>
<input ng:model="filter.email"/>
And your ng-repeat
would look like this
<ul ng:repeat="cust in customers | objFilter: filter">
<li>
<a href="mailto:{{cust.email}}">{{cust.firstName}} {{cust.lastName}}</a>
</li>
</ul>
Given, this is rather a corner case, but I thought I'd share.
You've a data-structure; it needs to be a collection, but it requires very SOLID fundamentals and its own methods due to expected growth, complexity, or other constraints. So you decide to prototype this object as an array.
var OrdersDataModel = function OrdersDataModel(orders) {
var thus = this;
var orders = orders || [];
function loadData(data) {
this.splice.apply(this, [0, this.length].concat(data || orders));
return this;
}
function serialize() {
var serialized = JSON.stringify(this);
return serialized;
}
function marshalData() {
var marshalled = JSON.parse(this.serialize());
return marshalled;
}
// export precepts
this.load = loadData;
this.serialize = serialize;
this.marshalData = marshalData;
return this;
};
OrdersDataModel.prototype = new Array();
This may suit your needs and perform well in your ng-repeat
-- until comes the need to filter
your "array":
<element ng-repeat="order in orders | filter:q" />
Your filter will fail (probably simply due to type-checks), and you'll wast a little time perhaps.
In my case, it can be easily remedied by modding my serialize
method from
var serialized = JSON.stringify(this);
-- to --
var serialized = JSON.stringify(this.slice(0));
Though, a much better way to accomplish my goal would be to decorate an array using either the Decorator Pattern or Constructor-Hijacking:
Instead of:
OrdersDataModel.prototype = new Array();
...
this.orders = new OrdersDataModel();
this.orders.load(serverData);
-- use --
this.orders = OrdersDataModel.apply([]);
this.orders.load(serverData);
A lot of this may seem a little extraneous, but I expect this (and its encompassing module/controller) to expand quickly, so I've stuck as much to SOLID as possible to keep it air-tight.
Hope this saves you some time!
Angular filters cannot handle an object of objects as input, however, to resolve the issue you are facing, you can add a watch to the catalog_filter. As follows:
$scope.$watch('catalog_filter', function (value) {
regex = new RegExp($scope.catalog_filter);
if (!$scope.catalog_filter) return true;
return regex.test(catalog.uid); /* can create multiple conditions based on the requirement*/
});
Doing this the filter acts on the input text and filters when the text changes.
You can use the toArrayFilter that converts the object to an array so it can be used by regular filters.
In your case it would be as:
<div ng-repeat="catalog in catalogs | toArray | filter:catalog_filter">
{{ catalog.$key }} - {{ catalog.someProp }}
<!-- code -->
</div>
Angular filters cannot handle an object of objects as input. ng-repeat can render them, but filters expect an array of objects. The easiest way to fix this is to let the server return an array without named keys. You could also transform the response in angular after each request (more expensive).