remove double results value from forEach (angular js) - angularjs

there is this thing who i am not figuring out,
i have a controller that return me an array of products propriety, that populate a field, so far i have done this:
$scope.categoryForFilter=[];
//product load
ProductService.listProducts().then(function(data) {
$scope.products = {
count: data.count,
list: data.aaData,
length: data.aaData.length
};
$scope.products.list.forEach(function (element) {
$scope.categoryForFilter.push({"id": element.category.id, "label": element.category.label})
});
});
but in this way i have multiple element.category.label in the options field that in my view is the following:
<select ng-model="categoryForFilter" ng-options="cat.label for cat in categoryForFilter" st-search="cat.label">
<option></option>
</select>
so how can i filter the results who have the same value in the array? anyone can help please?

You can use the unique filter, which is part of Angular UI.
You can also use this filter using lodash:
app.filter('unique', function() {
return function (arr, field) {
return _.uniq(arr, function(a) { return a[field]; });
};
});
Source

There is a real easy way to this by using lib like lodash with the function "uniq":
$scope.categoryForFilterWithUniqLabel = _.uniq($scope.categoryForFilter, 'label');

Related

Iterating over the array returned from a function in another service in angularjs

I have a function in my controller that is making a call to a function defined in a service shared by that controller. I am able to make a call correctly, but the function returns an array and i want to get individual item from that array. Here is the snippet
ctrl.loadInterfaces = function(driver) {
angular.forEach(ctrl.driverInterfaces, function(value, key){
ctrl.interfaces = myservice.driverInfo(driver, value);
})
};
So here, driverInfo() will return an array, something like ['one', 'two'] but now i want to split that and put individually 'one' and 'two' into ctrl.interfaces and not like an array. Any idea how should i do that?
By the way ctrl.driverInterfaces is just another static array declared in the controller.
My html
<select>
<option ng-repeat="interface in ctrl.interfaces"
value="{$ interface }">{$ interface $}</option>
</select>
Inputs are much appreciated. Thanks
Try this
ctrl.loadInterfaces = function(driver) {
ctrl.interfaces = [];
angular.forEach(ctrl.driverInterfaces, function(value, key){
ctrl.interfaces = ctrl.interfaces.concat(myservice.driverInfo(driver, value));
})
};

AngularJS, accessing ngRepeat values in filter predicate

My best attempts at finding a solution to this have come up empty. Basically I want to do something like this in my html:
<div data-ng-repeat="tag in allTags">
<h3>{{tag}}</h3>
<uib-accordion>
<div uib-accordion-group data-ng-repeat="item in displayItems | filter: tagFilter: tag">
tagFilter looks like this:
$scope.tagFilter = function(item, tag) {
if(item.tags.indexOf(tag) === -1) {
return false;
}
return true;
}
Each item in displayItems is an object that has an array of tags, so display items looks something like this:
[
{ title: "Item 1 Title", body: "Some escaped HTML Content", tags: ["tag1", "tag2", "tag3"]},
{ title: "Item 2 Title", body: "Some escaped HTML Content", tags: ["tag2", "tag4"] }
]
and I want it to appear under all headings to which it belongs. The problem is I can't figure out how to properly pass the value of 'tag' to tagFilter. In the code above the parameter tag in codeFilter is just equal to 0 no matter what.
The problem here is actually in the semantics of the Filter syntax. More specifically, the syntax you're using above is for when you're defining an Angular Filter using the ngApp.filter(...) syntax... i.e., a filter that's registered for the entire application and can be used anywhere. In that scenario the 3rd parameter in your filter statement is the value you want to pass to the registered filter.
In your case, you're defining a filter function inside your controller which changes how the filter works. Specifically, you cannot pass dynamic values to a filter function inside a controller. When you use a function as the filter expression, it has the following signature:
function(value, index, array) {}
and then gets called in the filter statement just by the function name, so:
array|filter:filterfunction - with no params or parenthesis.
value is the value of the current item (in the array) being filtered, index is the index of that item in the array, and array is the whole array being filtered. You cannot "pass" a value to this expression, but you can use a controller or scope variable if it applies. In your case it doesn't because the value you want to filter on is inside a repeater.
To achieve what you want, you need to make your $scope.tagFilter into an actual Angular Filter, like so:
ngApp.filter('tagFilter', function($filter)
{
return function(items, searchValue)
{
// initialize array to return
var filtered = [];
angular.forEach(items, function(obj)
{
// use filter to find matching tags (3rd param means EXACT search - set to False for wildcard match)
var objFilter = ($filter("filter")(obj.tags, searchValue, true))[0];
// If a matching tag was found, add it to the filtered array
if (objFilter) filtered.push(obj);
});
return filtered;
};
});
The above assumes you've saved your angular.module(...) bootstrap to a variable named ngApp. Once this filter is registered, your current filter syntax should work as expected!
Assuming displayItems is an array,
<div uib-accordion-group data-ng-repeat="item in displayItems.filter(tagFilter(tag))" >
should do the trick.
Figured out a way to do this based on this blog post: https://toddmotto.com/everything-about-custom-filters-in-angular-js/
Basically I had to create my own custom filter rather than using angulars predicate filter
The Javascript:
ng.module('faq').filter(
'tagFilter', function() {
return function(items, tag) {
return items.filter(function(item) {
if(item.tags.indexOf(tag) === -1) {
return false;
}
return true;
});
}
}
)
The HTML:
<div uib-accordion-group data-ng-repeat="item in displayItems | tagFilter: tag">
Still don't know why the original version was not working, so if anyone can answer that 10 points to them.

Filter on Onsen UI lazy repeat?

Is there a way to apply a search filter on an Onsen UI lazy repeat list ?
If we use for instance <input ng-model="search.$">
We can't directly apply | filter:search as it is not an ng-repeat.
Any idea ?
Thank you.
You need to filter the results return by the delegate object:
Simple example:
$scope.MyDelegate = {
configureItemScope: function(index, itemScope) {
itemScope.name = $scope.filteredItems[index].name;
},
calculateItemHeight: function(index) {
return 44;
},
countItems: function() {
return $scope.filteredItems.length;
}
};
In this codepen a large list of countries is filtered in this way:
http://codepen.io/argelius/pen/VLdGxZ

Always display a Key First with Angular

I have an ng-repeat like:
<div ng-repeat="(k, v) in query_p[$index].tags">
I would like it so that if the key (k) is a certain string, say "foo", that string always appears first in the list. It seems the getter option or orderBy only works with arrays. Anyone have an example of how they might achieve this?
Basically you have an unordered object, and you want it to have some kind of order.
To do that you need to create a function that returns some ordered object.
myApp.filter('promote_foo', function() {
return function(object, comp) {
console.log(object);
console.log(comp);
var ordered = [];
for (var key in object) {
var obj = {
key: key,
value: object[key]
};
if (key === comp)
ordered.splice(0,0,obj);
else
ordered.push(obj);
}
console.log(ordered);
return ordered;
};
});
the function takes a parameter which will promote and object if the key matches it. Now I only call it in the controller directly, but you could use it just like any angular filter.
$scope.order = $filter('promote_foo')($scope.data, 'foo');
Also, you can play with the fiddle here.
Hope this helped!

Adding a computed field to every element of an array in an AngularJS model

I'm pulling an array of users into my AngularJS model from a JSON datasource. This data is being rendered in a table, and I'd like to create a column that is computed from two values of the existing user object, without modifying my underlying data service.
// My model
function UserListCtrl($scope,$http) {
$http.get('users').success(function(data) {
$scope.users = data;
});
};
In my partial template, I know I can do something like this:
<tr ng-repeat="for user in users">
<td>{{user.data / user.count | number:2}}</td>
</td>
But I'd rather add that field into the model, so I can use it like so:
<td>{{user.amplification}}</td>
How do I add the "amplification" field to every user in my model?
As an aside, is it possible to use the orderBy filter on something like this:
<td>{{user.data / user.count | number:2}}</td>
You can eather:
Just after loading user do:
$http.get('users').success(function(data) {
$scope.users = data;
$scope.user.amplification() = function() { return $scope.user.data / $scope.user.count; }
});
And use as {{user.amplification()}}
Anywhere at controller:
$scope.$watch('user', function() {
$scope.userAmplification = $scope.user.data / $scope.user.count;
}, true);
$http.get
Or if user.data/count do not change, do same as 1. but staticly calculate:
$http.get('users').success(function(data) {
$scope.users = data;
$scope.user.amplification = $scope.user.data / $scope.user.count;
});
And OrderBy could be used on any expression (uncluding result of other filter)
If you don't need your amplicification() function to update when the data and count properties on your user update, you can do something like this in your controller:
$scope.users.forEach(function(user) {
user.amplification = function() {
return user.data / user.count;
};
});
Adding a second answer as I feel it's appropriate as it's distinct from my first one.
After a little looking around, I found the method I originally posted falls over if you try to add new rows dynamically, or new elements to the array which depend on the computed value. This is because the $scope.array.forEach() will only run when the controller is created.
The best way to solve this problem is to create a properly defined object which contains the options you want. e.g.
function Task(id, name, prop1, prop2) {
this.id = id;
this.name = name;
this.prop1 = prop1;
this.prop2 = prop2;
this.computedProperty = function () {
return this.prop1 + this.prop2;
};
}
This is far more flexible as each new object created will have the new property.
The only downside is that in your ajax success callback, you'll need to pass each of your users into your 'Users()' constructor.
What worked for me was to add a loop and add the property to each item in that loop. I used a property of the controller but I am sure you can use scope the way you are approaching it in the question.
function(result) {
self.list = result;
angular.forEach(self.list, function(item) {
item.hasDate = function() {
return this.TestDate != null;
}.bind(item); // set this context
});
}
Then in my markup I just used it like this.
<div ng-repeat...>
<div ng-show="item.hasDate()">This item has a date.</div>
</div>

Resources