I'm getting info from an API, and i want to filter some results:
This is my code to consume the API:
function listData() {
$http.get('/api/Invoices?')
.then(function(data) {
$scope.list = data.data.Response;
});
}
Then, with ng-repeat, i feed a list:
<tr ng-repeat="info in list">
<th>{{info.Id}}</th>
<th>{{info.Name}}</a></th>
<th>{{info.value}}</th>
<th>{{info.FiscalFolio}}</th>
</tr>
I want to filter for Id when the API is consumed. Someone sugest me to use Array#filter(), but i cannot make it work. This is my test, but i'm not sure is right:
function listData() {
$http.get('/api/Invoices?')
.then(function(data){
$scope.list = data.data.Response;
var pool = $scope.list;
var ajax = pool.filter(function(xavier) {
return xavier.StatusId === 1;
});
});
}
I've got two questions:
Is correct the way i'm working with the filter?
Did i need to put the variable on the html view?
Can you help me with an example?
filter() returns a new array. Just filter the Response array as you assign it to $scope.list if you don't care about any of the other data
$scope.list = data.data.Response.filter(function(xavier){
return xavier.StatusId === 1;
});
If you need to store the Response array you can assign it to another variable for using another filter on it later on
Related
Iam trying to create a custom filter to filter matching array of values in angularjs. Array Structure below
["tag1","tag2"]
Now I need to filter all objs having tags matching id1,id2.. Below is the filter I have tried
var autoFilter = angular.module("autoFilters",[]);
autoFilter.filter('arrayData', function (){
return function(){
return ["id1","id2"];
}
//$scope.arrayValues = ["id1","id2"];
});
and UI code below
<li style="cursor:pointer" ng-cloak class="list-group-item" ng-repeat="values in suggestionResults | arrayData">{{values.id}} -- {{values.title}}</li>
But Data is not showing up. Can you help me out where Iam doing wrong. Plunker Code available below
plunker here
see the code below :) This is not the best approach in my opinion and will definitely have some performance issue with larger lists, but it does the work (now I used indexOf(2) but there you can pass any truthy/falsy argument)
var autoFilter = angular.module("autoFilters",[]);
autoFilter.controller("filterController",['$scope','$http', function ($scope,$http) {
$scope.searchSuggest = function(){
//$http({method: 'GET', url: 'json/searchSuggestions.json'}).success(function(data) {
$http.get("assets.json").then(function(response) {
//var str = JSON.stringify(response);
//var arr = JSON.parse(str);
$scope.suggestionResult = response.data;
console.log($scope.suggestionResult);
//$scope.arrayData = ["asset_types:document/data_sheet","asset_types:document/brochure"];
}).catch(function activateError(error) {
alert('An error happened');
});
}
$scope.showProduct = function(){
}
}]);
autoFilter.filter('arrayData', function (){
return function(data){
// if you are using jQuery you can simply return $.grep(data, function(d){return d.id.indexOf('2') >-1 });
return data.filter(function(entry){
return entry.id.indexOf('2') > -1
})
}
});
Having experienced working with large lists I would, however, suggest you to avoid using a separate filter for this and rather manipulate it in the .js code. You could easily filter the data when you query it with your $http.get like:
$scope.suggestionResult = response.data.filter(function(){
return /* condition comes here */
}
This way you are not overloading the DOM and help the browser handling AngularJS's sometimes slow digest cycle.
If you need it to be dynamic (e.g. the filtering conditions can be changed by the user) then add an ng-change or $watch or ng-click to the modifiable information and on that action re-filter $scope.suggestionResult from the original response.data
I am using ng-repeat to display my data. That is working fine.
One of the data fields in my ng-repeat result set is an array of items.
Example: {x:1, y:[2,3,4]}
I would like to filter by data by data in the array. I can easily filter by the non array data, but I am having trouble when I try to filer by looking inisde in the array.
Here is my markup
ng-repeat = "con in cons | filter: usrSingle.username in con.conPlayerList"
(edited markup to match my example better ng-repeat = "con in cons | filter: '3' in con.y" )
usrSingle is a scope in this controller I can access. I don't get any errors and I can't seem to find examples of this.
More code was requested and here it is below. I forgot to mention this is a MEAN app. I have MongoDB serving the data. I use a REST API for my data calls.
(EDIT) code from the angular module:
// call to the api for data
app.factory('usrService', function ($resource) {
return $resource('/api/users', {});
});
// factory to hold user data between controllers
app.factory('usrTracker', function () {
var vUsrProfile = {
usrSingle: 'usrSingle'
};
return {
getProperty: function () {
return vUsrProfile;
},
setProperty: function (value) {
vUsrProfile = value;
}
};
});
// angular controller
app.controller('usrPageController', function ($scope, usrTracker, conService, postService) {
var usrSingle = usrTracker.getProperty();
$scope.usrSingle = usrSingle;
$scope.cons = conService.query();
$scope.posts = postService.query();
});
(FINAL EDIT) The answer marked for this is a good solution. But I went another direction. I used the mongoDB $unwind aggregation to explode my data, like below.
code from API file:
// get
.get(function (req, res) {
// unwind on the conPlayerList array field
Con.aggregate([{ $unwind: "$conPlayerList" }], function (err, cons) {
return res.json(cons);
});
})
Then I filtered on the user I was looking for. Changing the HTML Angular markup to this
ng-repeat="con in cons | filter:{conPlayerList:usrSingle.username}"
to match my example better, by removing my specific code, it would be:
ng-repeat="con in cons | filter: {y:'3'} "
You can use Angular's built in filter logic on the array, BUT bear in mind this is not an exact match and will match against an array entry of 300 if usrSingle is set to 3, see example in Fiddle.
<div ng-repeat = "con in cons | filter:{y:usrSingle} ">...</div>
To match exactly against array elements you can use filter: and a predicate function on the controller i.e.
Controller (Fiddle):
app.controller('usrPageController', function ($scope) {
$scope.filterFn = filterFn;
$scope.usrSingle = 3;
$scope.cons = [
{x:0, y:[1,2,3]},
{x:1, y:[2,3,4]},
{x:2, y:[3,4,5]},
{x:3, y:[4,5,6]},
{x:4, y:[5,6,7]}
];
function filterFn(con){
return con.y.indexOf($scope.usrSingle) > -1;
}
});
View:
<div ng-repeat = "con in cons | filter:filterFn">...</div>
Alternatively you can create a custom filter which can be reused elsewhere in your app and pass usrSingle in as a parameter:
Filter (Fiddle):
app.filter('customFilter', function() {
return function(input, value) {
var result = [];
input.forEach(function(item){
if(item.y.indexOf(value) > -1){
result.push(item);
}
});
return result;
};
});
View:
<div ng-repeat = "con in cons | customFilter:usrSingle">...</div>
I have a rather simple question. I have a simple controller and its $scope.coords = []; renders JSON in HTML:
[24.43359375, 54.6611237221]
[25.2905273438, 54.6738309659]
[25.3344726562, 54.6102549816]
[25.2685546875, 54.6801830971]
[25.2960205078, 54.6611237221]
How can I render that JSON not in html, but in my controller itself ? The code looks like that. Please see the comment in code:
propertyModule.controller('propertyController', ['$scope', 'Property', function ($scope, Property) {
// Query returns an array of objects, MyModel.objects.all() by default
$scope.properties = Property.query();
// Getting a single object
$scope.property = Property.get({pk: 1});
$scope.coords = [];
$scope.properties = Property.query({}, function(data){
console.log(data);
angular.forEach(data , function(value){
$scope.coords.push(value.coordinates);
});
});
$scope.positions = //$Resource('realestate.property').items();
[
[54.6833, 25.2833], [54.67833, 25.3033] // those coordinates are hardcoded now, I want them to be rendered here by $scope.coords
];
}]);
First off, you're showing us a bunch of arrays, not a JSON document. But since your code seems to be working, I'll assume you do have a valid JSON to work with.
You need to consider the fact that you are making an asynchronous request here :
$scope.properties = Property.query({}, function(data) {
console.log(data);
angular.forEach(data , function(value){
$scope.coords.push(value.coordinates);
});
});
This means you won't be able to fetch data from $scope.coords before anything has arrived.
There are several ways to solve that :
You could simply fetch data while you're still in the loop :
angular.forEach(data , function(value) {
$scope.coords.push(value.coordinates);
if('your condition') {
$scope.positions.push(value.coordinates);
}
});
You could use a promise, see the angular doc.
Or you could watch over $scope.coords with $scope.$watch.
tl:dr
How can I populate an ng-table including 'select' filters using ajax/json?
Plunk showing the problem: http://plnkr.co/Zn09LV
Detail
I am trying to get to grips with AngualrJS and the ng-table extension and although I can get some nice tables with working filters and such when I'm using static data defined in the javascript - once I get to trying to load real data into the table I hit a snag.
The main body of the ng-table is populated correctly and as long as I only use the text filter everthing seems to be working:
<td data-title="'Name'" filter="{ 'Name': 'text' }" sortable="'Name'">
{{user.Name}}
</td>
Works just fine.
However, if I update this to use the select filter:
<td data-title="'Name'" filter="{ 'Name': 'select' }" sortable="'Name'" filter-data="Names($column)">
{{user.Name}}
</td>
I run into a syncronisation issue in that the Names variable is always evaluated before the data has returned from the server. (Possibly the Names varibale is evaluated before the request to the server is even sent.) This means I get an empty list for the filter.
Once the data returns from the server - I can't seem to find a way of updating the select filter. Re-running the code that creates the filter list initially seems to have no effect - I'm not sure how to trigger the ng-table to re-check its filters so the updated variable isn't read.
I also can't figure out a way to postpone the evaluation of the variable until after the async call is complete.
For my javascript I have pretty much used the example ajax code from the ng-table GitHub page and added onto it the example code for the select filter.
$scope.tableParams = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
sorting: {
name: 'asc' // initial sorting
}
}, {
total: 0, // length of data
getData: function($defer, params) {
// ajax request to api
Api.get(params.url(), function(data) {
$timeout(function() {
// update table params
var orderedData = params.sorting ?
$filter('orderBy')(data.result, params.orderBy()) :
data.result;
orderedData = params.filter ?
$filter('filter')(orderedData, params.filter()) :
orderedData;
$scope.users = orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count());
params.total(orderedData.length); // set total for recalc pagination
$defer.resolve($scope.users);
}, 500);
});
}
});
var inArray = Array.prototype.indexOf ?
function (val, arr) {
return arr.indexOf(val)
} :
function (val, arr) {
var i = arr.length;
while (i--) {
if (arr[i] === val) return i;
}
return -1
};
$scope.names = function(column) {
var def = $q.defer(),
arr = [],
names = [];
angular.forEach(data, function(item){
if (inArray(item.name, arr) === -1) {
arr.push(item.name);
names.push({
'id': item.name,
'title': item.name
});
}
});
def.resolve(names);
return def;
};
I've tried a few attempts at adding on an additional $q.defer() and wrapping the initial data get followed by the $scope.names function - but my understanding of promise and defer isn't strong enough to get anything working.
There are a few notes on GitHub suggesting this is a bug in ng-table, but I'm not sure if that's the case or I'm just doing something daft.
https://github.com/esvit/ng-table/issues/186
Pointers on how to procede greatly appreciated
-Kaine-
I had a similar but slightly more complex issue. I wanted to be able to update the list of filters dynamically which seemed totally doable since they should just in a $scope variable anyway. Basically, I expected that, if I have $scope.filterOptions = []; then I could set filter-data="filterOptions" and any update to that list would be automatically reflected. I was wrong.
But I found a solution that I think is pretty good. First, you need to override the ngTable select filter template (in case you don't know how to do this, it involves using $templateCache and the key you need to override is 'ng-table/filters/select.html').
In the normal template, you'll find something like this ng-options="data.id as data.title for data in $column.data" and the problem with that is that $column.data is a fixed value that won't change when we update $scope.filterOptions.
My solution is to pass only the $scope key as filter-data instead of passing the whole list of options. So, instead of filter-data="filterOptions", I'll pass filter-data="'filterOptions'" and then, put a small change in the template like: ng-options="data.id as data.title for data in {{$column.data}}".
Obviously, this is a significant change to how the select filter works. In my case, it was for a very small app that only had one table but you may be concerned that a change like this will break your other selects. If that's the case, you may want to build this solution into a custom filter instead of just overriding 'select'.
You can achieve that with a custom filter:
The code for the standard select filter on ngtable says:
<select ng-options="data.id as data.title for data in column.data"
ng-model="params.filter()[name]"
ng-show="filter == 'select'"
class="filter filter-select form-control" name="{{column.filterName}}">
</select>
When you call this data you pass: filter-data="names($column)" and ngtable takes care of getting the data for you. I don't know why this does not work with an external resource. I bet it has something to do with the $column and the promise, as you pointed out.
I did a quick workaround in my code to avoid that. Writing my own select filter template like:
<select id="filterTest" class="form-control"
ng-model="tableParams.filter()['test']"
ng-options="e.id as e.title for e in externaldata">
</select>
You fetch this externaldata in your controller:
$scope.externaldata = Api.query(); // Your custom api call
It works perfectly, but I do have an id on my data, so no need of the name function.
I understand this solution is not optimal. Let's see if somebody writes here more than this 'workaround' and enlightens us. Even esvit is here sometimes ;)
This works for me:
HTML:
<td data-title="'Doc type'" filter="{ 'doc_types': 'select' }" filter-data="docTypes()" sortable="'doc_types'">
{{task.doc_type}}
</td>
AngularJS:
$scope.docTypes = function ($scope)
{
var def = $q.defer();
//var docType = [
// {'id':'4', 'title':'Whatever 1'},
// {'id':'9', 'title':'Whatever 2'},
// {'id':'11', 'title':'Whatever 3'}
//];
// Or get data from API.
// Format should be same as above.
var docType = $http.get('http://whatever.dev', {
params: { param1: data1 }
});
//Or with Restangular
var docType = Restangular.all('/api/doctype').getList();
def.resolve(docType);
return def;
};
As mentioned by #AndiĆ³n You can achieve with custom filter.
It is easy to achieve Asynchronous data population with Promises (the $q service in Angular), interesting Andy Article about Promises
You can amend the $scope.names method and add $http service that return the asynchronous data and resolve the deferred object as:
$scope.names = function(column) {
var def = $q.defer();
/* http service is based on $q service */
$http({
url: siteurl + "app/application/fetchSomeList",
method: "POST",
}).success(function(data) {
var arr = [],
names = [];
angular.forEach(data, function(item) {
if (inArray(item.name, arr) === -1) {
arr.push(item.name);
names.push({
'id': item.name,
'title': item.name
});
}
});
/* whenever the data is available it resolves the object*/
def.resolve(names);
});
return def;
};
I encountered a similar issue but did not want to make the additional AJAX call to get the filter values.
The issue with the OP's code is that filter-data function executes before $scope.data is populated. To get around this I used the Angular $watch to listen for changes on $scope.data. Once $scope.data is valid the filter-data is populated correctly.
$scope.names2 = function () {
var def = $q.defer(),
arr = [],
names = [];
$scope.data = "";
$scope.$watch('data', function () {
angular.forEach($scope.data, function (item) {
if (inArray(item.name, arr) === -1) {
arr.push(item.name);
names.push({
'id': item.name,
'title': item.name
});
}
});
});
def.resolve(names);
return def;
};
The original plunk forked with the change: http://plnkr.co/edit/SJXvpPQR2ZiYaSYavbQA
Also see this SO Question on $watch: How do I use $scope.$watch and $scope.$apply in AngularJS?
I solved the issue with $q.defer() as mentioned by Diablo
However, the is code actually pretty simple and straightforward:
in HTML:
<td ... filter-data="countries">
in controller:
$scope.countries = $q.defer();
$http.get("/getCountries").then(function(resp){
$scope.countries.resolve(resp.data.data);
})
"First, you need to override the ngTable select filter template (in case you don't know how to do this, it involves using $templateCache and the key you need to override is 'ng-table/filters/select.html')."
I added the overrided script below the script of ng-table and everything worked well...
<script id="ng-table/filters/select.html" type="text/ng-template">
<select ng-options="data.id as data.title for data in {{$column.data}}" ng-table-select-filter-ds="$column" ng-disabled="$filterRow.disabled" ng-model="params.filter()[name]" class="filter filter-select form-control" name="{{name}}"> <option style="display:none" value=""></option> </select>
</script>
What I did is just put the select tag with values and have the ng-model return the values for the filter.
This was helpful since I needed to translate the plain text.
<td data-title="'Status'| translate" ng-bind = "("cc_assignment_active"== '0') ? ('Inactive' | translate) : ('Active'| translate)"
filter="{ cc_assignment_active: 'select3'}" >
</td>
<script id="ng-table/filters/select3.html" type="text/ng-template">
<select class="filter filter-select form-control" ng-model="params.filter()[name]" name="{{name}}">
<option active value="" translate>---All---</option>
<option value="1" translate>Active</option>
<option value="0" translate>Inactive</option>
</select>
I'm taking my first steps with AngularJS calling an API that would send back a JSON object with the data that I need. I calls the API and takes the response and print it on the HTML, iterating as needed.
But I haven't found a way on the actual Javascript file, to iterate to lets say, group and sum items, or to print the actual values of the JSON to the console.
app.js
angular.module('dashboard', ['ngResource']);
function DashboardCtrl($scope, $resource) {
$scope.dailyReports = $resource('http://myurl.com/app_dev.php/:action',
{action:'dailyreports', appid:'#appid', begindate:'#begindate', enddate:'#enddate'});
$scope.loadData = function() {
$scope.dailyreportsResults = $scope.dailyReports.get({appid:$('#sel-apps').val(), begindate:$('#fecha-inicial').val(), enddate:$('#fecha-final').val()});
//it works!!!!
};
$scope.iterate = function() {
for (i=0; i < $scope.dailyreportsResults.data.length; i++){
$scope.periodUnits += $scope.dailyreportsResults.data[i].units;
console.log($scope.periodUnits);
//It doesnt work :(
}
};
index.html
ng-repeat="data in dailyreportsResults.data"
{{data.countryName}}
{{data.units}}
What am I doing wrong?
Thanks guys!
I'm not sure what your data looks like, but maybe you should be using 'for in' instead of incrementing an array.
$scope.iterate = function() {
for (x in $scope.dailyreportsResults.data){
$scope.periodUnits += $scope.dailyreportsResults.data[x].units;
console.log($scope.periodUnits);
}
};
also, I think your ng-repeat should be attached to an element.
<div ng-repeat="data in dailyreportsResults.data">
{{data.countryName}}
{{data.units}}
</div>