Angular: mixing ng-repeat, arrays, and objects with ids - arrays

I have a collection of items which I'm using with ng-repeat and filtering.
Each item has a unique Id, and is stored in an array:
[ {id: 387, name: "hello"}, {id: 2878, name: "world"}, ...]
But now I need to reference these objects by their unique id instead of array index (as well as use ng-repeat with filters).
So I tried using a sparse array:
array[387] = {name: "hello"}; array[2878] = {name: "world"}...
But ng-repeat craps itself because it sees 'duplicate' undefined keys. (I also tried using 'track by' but ng-repeat still didn't like it).
I can't use an object with ng-repeat because filters don't work.
Soooo, how can I both use ng-repeat with filters, and be able to reference the items by id? The only option I can think of is to have a second data structure to map id's to indexes.
Thanks for your help!
Chris.

Try this:
'track by $index'
ng-repeat="name in Lists track by $index"

When I need to achieve this sort of thing, I do some quick preprocessing of the data grabbed, and create the necessary lookup. For example:
var data = [
{ id: 55, name: 'some name' },
{ id: 65, name: 'another name' }
];
// create a lookup
var lookup = {};
angular.forEach(data, function(item){
lookup[data.id] = item;
});
You could manipulate the data any way you want here, but by referencing the actual array items, you can bind either the data or lookup (or both) to $scope, and any changes will be exhibited on both structures. Bear in mind this won't happen if you add/remove items from the array, as you need to manage the addition and removal of items from the lookup.

I can see your ids are integer. angular filters not working because angular use string comparison for number.
Go to the angular.js search a function name filterFilter. line 13991 for v1.2.10. Then you can use objects in your ng-repeat.
function filterFilter() {
...
var search = function(obj, text){
if (typeof text == 'string' && text.charAt(0) === '!') {
return !search(obj, text.substr(1));
}
switch (typeof obj) {
case "boolean":
case "number": // <-- here
// add return obj === text;
case "string":
return comparator(obj, text);
case "object":
switch (typeof text) {
case "object":
return comparator(obj, text);
default:
for ( var objKey in obj) {
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
return true;
}
}
break;
}
return false;
case "array":
for ( var i = 0; i < obj.length; i++) {
if (search(obj[i], text)) {
return true;
}
}
return false;
default:
return false;
}
};

Related

How to Filter Restructured Data in Polymer (dom-repeat)

I am trying to filter an array that is being reindexed on-the-fly.
I would like to have a single input field that matches strings on multiple properties.
<paper-input value="{{val}}" placeholder="Filter Cards"></paper-input>
<template is="dom-repeat" items="[[restructure(data)]]" initial-count="2" filter="{{filter(val, data)}}">
<card-items data="[[item]]" items="[[item.items]]" links="false"></card-items>
</template>
...
This function restructures the data to be formatted for a card layout.
returnInvoices(data) {
let newData = [];
for (let i = 0; i < data.length; i++) {
let noOrder = data[i].noOrder;
if (!newData[noOrder]) {
newData[noOrder] = {
idMaster: data[i].idMaster,
itemId: data[i].itemId,
description: data[i].description,
noOrder: noOrder,
items: []
};
}
newData[noOrder].items.push('{' +
'"idMaster":"' + data[i].idMaster + '",' +
'"itemId":"' + data[i].itemId + '"}');
}
return newData.filter(val => val).sort((a, b) => {return b.noInvoice - a.noInvoice}) // reindex array
}
I would like this function to return objects in the array whom have properties that match a string.
filter(val, data) {
if (!val) return null;
else {
val = val.toLowerCase();
// not sure what to do here
// would like to filter on all object properties (from data)
return data[0];
}
}
...
Example
if(val == 1) return data[0] & data[1];
if(val == 'Item') return data[0] & data[2];
For data array
let data = [
{"itemId": "1", "description": "Nice Item", "noOrder": 123},
{"itemId": "2", "description": "Good Product", "noOrder": 123},
{"itemId": "3", "description": "Fine Item", "noOrder": 444}
}
...
How can I filter strings on all 3 properties?
How can I use the filter as an intermediate function to the restructuring?
The documentation for dom-repeat's filter property includes following statements:
The function should match the sort function passed to Array.filter. Using a filter function has no effect on the underlying items array.
And the Array.filter is documented as
Function is a predicate, to test each element of the array. Return true to keep the element, false otherwise.
So from your filter func just return true if any of the properties matches input and false otherwise, something like
filter(item) {
let val = this.val;
// if the filter is empty show everything
if (!val) return true;
// otherwise see is there a match
val = val.toLowerCase();
return // for the "description" use "contains" logic
(item.description.toLowerCase().includes(val)) ||
// for the "noOrder" use "starting" logic
(item.noOrder.toString().indexOf(val) == 0)
// add matching conditions here ...
}
Now to trigger the filtering you must observe the properties which trigger filtering, ie your html would be like
<paper-input value="{{val}}" placeholder="Filter Cards"></paper-input>
<template is="dom-repeat" filter="[[filter]]" observe="val" items="[[restructure(data)]]" initial-count="2">
<card-items data="[[item]]" items="[[item.items]]" links="false"></card-items>
</template>
BTW why do you push items into newData as strings? Why not as objects, ie
newData[noOrder].items.push({
idMaster: data[i].idMaster,
itemId: data[i].itemId
});
and I think you can lose the newData.filter(val => val) step...

Infinite digest loop inside custom filter of angularJS

I am displaying a list of tables and each row is also expandable. I want to filter the list using the property of expandable table. For example if the main table show the general info of student and expandable table shows the marks of subject for that student. I want to filter the complete table by marks column. So the filtering should display the parent table with matched marks.
var students = [{
name: "neha",
address: "abc"
marks: [{
subject: "english",
marks: 80
}, {
subject: "hindi",
marks: 60
}]
}, {
name: "sneha",
address: "xyz"
marks: [{
subject: "english",
marks: 70
}, {
subject: "math",
marks: 50
}]
}
For this
I am using custom filter to filter the list. Inside custom filter I am using "filterFilter" to filter the marks array.
filterBy is the property which keeps track of property against which the value will be tested.
For example the values can be
1) filterBy = {property: "marks.subject", value: "hindi"} //filter on nested table
2) filterBy = {property: "name": value: "neha"} //filter on parent table
var app = angular.module("MyApp", []);
app.filter("filterTable", function(filterFilter) {
return function(list, filterBy) {
var filteredList = [],
len,
i,
j,
property,
present,
tempList,
listCopy,
properties = [];
//creating a copy as filter will update the list and rest filter will not work properly
listCopy = angular.copy(list);
len = listCopy.length;
if(filterBy.value) {
properties = filterBy.property.split(".");
//if filter on nested table
if(properties.length > 1) {
property = properties[1];
for ( i = 0; i < len; i++) {
tempList = {};
//using filterFilter defined by angularjs
listCopy[i].disks = filterFilter(list[i].disks, { [property] : filterBy.value });
console.log(list[i].disks);
if(listCopy[i].disks.length) {
filteredList.push(listCopy[i]);
}
}
} else {
property = filterBy.property;
for ( i = 0; i < len; i++) {
if(list[i][property].indexOf(filterBy.value) > 0) {
filteredList.push(list[i]);
}
}
}
return filteredList;
} else {
return list;
}
};
});
But this is going into infinite digest cycle. I have spent a long time on this and still not able to resolve. Please help me with it.
Thanks in advance.
This happens because you are deep copy array.
Angular's ngRepeat doesn't realize that those objects are equal because ngRepeat tracks them by identity. New object leads to new identity. This makes Angular think that something has changed since the last check, which means that Angular should run another check (aka digest).
Try update logic of filter without angular.copy()
Update:
Instead of angular.copy use flat copy:
var newArray= oldArray.slice(); // clones the array and returns the reference to the new array.

recursive json tree filter

I'm using angular 1.5.9 and I have a JSON object like this:
var data=[
{"id":1,"name":"object 1", childs:[
{"id":51,"name":"object 51", childs:[]},
]},
{"id":2,"name":"object 2", childs:[
{"id":11,"name":"object 11", childs:[]},
{"id":12,"name":"object 12", childs:[
{"id":13,"name":"object 100", childs:[]},
]},
]},
{"id":3,"name":"object 3", childs:[]},
{"id":1,"name":"object 1", childs:[]}
];
I need to filter this tree so that I get all the elements (branches or leaves whose name contains the filter string and all the parents.
i.e: filtering for "100" will result in
[
{"id":2,"name":"object 2", childs:[
{"id":12,"name":"object 12", childs:[
{"id":13,"name":"object 100", childs:[]},
]},
]},
]
This data will then be rendered in a customized tree directive using ng-repeat over the data itself
I'm wondering if someone can suggest a clean and efficent way to achieve this. All the code I've written seems to be too complex and end up traversing the tree so many times that a better way must exist.
actual metacode is somewhat like
* sequenially read ech JSON object in main array
* if name matches add a property (visible:true) and travel back to the beginning setting all the parents' visible:trre
* if childs array contain something, re-call the main filter function to scan all childrens
This could be somewhat acceptable for small datasets, but on large object will probably be very inefficient.
You can just write some recursive javascript for this, something like:
function findObjectAndParents(item, name) {
if (item.name.split(' ')[1] == name) {
return true;
}
for (var i = 0; i < item.childs.length; i++) {
if (findObjectAndParents(item.childs[i], name)) {
return true;
}
}
return false;
}
And use it like this:
var searchName = "100";
var filtered = data.filter(function(item) {
return findObjectAndParents(item, searchName);
});
Ref the answer:
A Javascript function to filter tree structured json with a search term. exclude any object which donot match the search term
function search(array, name) {
const s = (r, { childs, ...object }) => {
if (object.name.includes(name)) {
r.push({ object, childs: [] });
return r;
}
childs = childs.reduce(s, []);
if (childs.length) r.push({ ...object, childs });
return r;
};
return array.reduce(s, []);
}
console.log(JSON.stringify(search(data, '100'),0,2));

Smart Table st-search inside nested objects

Is there any way to search inside nested elements in smart-table? I feed the table with data from a REST Api that consists of the following form:
{
"id": 1,
"small_name": "Foo",
"large_name": "Bar Foo",
"variants": [{"value": "0"}, {"value": "1"}]
}
What I want to achieve is the possibility to filter the data through the value property of the objects inside the variants.
From the Smart Table documentation:
"The stSetFilter replaces the filter used when searching through Smart Table. When the default behavior for stSearch does not meet your demands, like in a select where one entry is a substring of another, use a custom filter to achieve your goals."
http://lorenzofox3.github.io/smart-table-website/
There is also an example available at that site.
I'll post the solution for my problem, maybe it can help someone.
angular.module('YourModule').filter('CustomFilter', [
'$parse',
function ($parse) {
return function(items, filters) {
console.log(items, filters);
var itemsLeft = items.slice();
Object.keys(filters).forEach(function (model) {
var value = filters[model],
getter = $parse(model);
itemsLeft = itemsLeft.filter(function (item) {
if (model === 'value') {
var variants = item.variants.filter(function (variant) {
return getter(variant).match(value);
});
return variants.length;
} else {
return getter(item).match(value);
}
});
});
return itemsLeft;
}
}
])

How to filter records with non zero value in AngularJS

I have a following array of records. And i want to exclude records with zero value. In this case, exclude only 'pqr' and get 'abc' and 'xyz' as output.
$scope.myArray = [{
name: 'abc',
value: 2
}, {
name: 'pqr',
value: 0
}, {
name: 'xyz',
value: 104
}]
$scope.filteredRecords = $filter('filter')($scope.myArray, {
'value': '!0'
});
And the output filteredRecords contains
[{"name":"abc","value":2}]
But the problem is I don't want to exclude the 'xyz' record. I just want to exclude records with value exactly as 0. But it looks like angular is comparing substring not the complete value.
How do i use default angular filter to get records with non zero value
(without writing a custom filter)
plunker for above code.
As stated in the doc, you can use a predicate function instead of an array:
$scope.filteredRecords = $filter("filter")($scope.myArray, function(value, index, array) {
return value.value !== 0;
});
I would suggest just using Array.prototype.filter()
$scope.filteredRecords = $scope.myArray.filter(function(item) {
return item.value !== 0;
});
You can use filter method of javascript to do this:
$scope.filteredRecords = $scope.myArray.filter(function (item) {
return item.value !== 0;
});
Here is a working fiddle

Resources