Let's say i have such two objects:
first:
[
{
id: "123",
title: "123",
options: []
},
{
id: "456",
title: "456",
options: [
{
id: "0123",
title: "0123",
options: []
}
]
},
{
id: "789",
title: "789",
options: []
},
]
and second
[
{
id: "123",
title: "123",
options: []
},
{
id: "789",
title: "789",
options: []
},
]
as you could see in second array i'm missing this part:
{
id: "456",
title: "456",
options: [
{
id: "0123",
title: "0123",
options: []
}
]
}
how it would be right and better to iterate and find missing elements in angular?
You can do it like
<div ng-app>
<div ng-controller="MyCtrl">{{availableGroups}}
</div>
</div>
js code
function MyCtrl ($scope) {
$scope.groups = [
{
id: "123",
title: "123",
options: []
},
{
id: "456",
title: "456",
options: [
{
id: "0123",
title: "0123",
options: []
}
]
},
{
id: "789",
title: "789",
options: []
},
];
$scope.assignedGroups = [
{
id: "123",
title: "123",
options: []
},
{
id: "789",
title: "789",
options: []
},
];
$scope.availableGroups = (function () {
var assignedGroupsIds = {};
var groupsIds = {};
var result = [];
$scope.assignedGroups.forEach(function (el, i) {
assignedGroupsIds[el.id] = $scope.assignedGroups[i];
});
$scope.groups.forEach(function (el, i) {
groupsIds[el.id] = $scope.groups[i];
});
for (var i in groupsIds) {
if (!assignedGroupsIds.hasOwnProperty(i)) {
result.push(groupsIds[i]);
}
}
return result;
}());
}
Here is working jsFiddle
Thanks
Let's say that the first array is named first and the second second. Now sort them first:
function comp(a, b){
if(a.id < b.id) return -1;
if(a.id > b.id) return 1;
return 0;
}
first.sort(comp);
second.sort(comp);
Then iterate through them to find missing elements:
var missing = {};
for(var i = 0, j = 0; i < first.length; ++i){
if(first[i].id == second[j].id){
j++;
continue;
}
missing.push(first[i]);
}
The missing array now contains objects that is in the first array but not the second one.
Note that I didn't use AngularJS; it's plain Javascript.
Related
I have a menu with this structure
item1
item2
childrenOfItem2
childrenOfchildren1
childrenOfchildren2
HELLOchildrenOfchildren3
childrenOfItem2
childrenOfItem2
HELLOitem3
item4
childrenOfItem4
HELLOchildrenOfItem4
item5
childrenOfItem5
So, Id' like to get all the items that have the word "HELLO" and what I'm doing is a loop over the first items, then, another loop and then another loop, is there any other way of doing it? Since let's say that if we add another level of depth in the menu it will not work,
Thank you!
Edited: adding JS for better understanding
const matchName = (item, word) =>
item?.title?.toLowerCase()?.includes(word?.toLowerCase());
const filter = (word = "", arr = []) => {
const listOfItems = [];
arr.forEach((item) => {
if (matchName(item, word)) {
listOfItems.push(item);
} else if (item?.children?.length > 0) {
const newSubItem = [];
item.children.forEach((subItem) => {
if (matchName(subItem, word)) {
newSubItem.push(subItem);
} else if (subItem?.children?.length > 0) {
const newSubSubItems = [];
subItem.children.forEach((subsubItem) => {
if (matchName(subsubItem, word)) {
newSubSubItems.push(subsubItem);
}
});
if (newSubSubItems?.length > 0) {
newSubItem.push({ ...subItem, children: newSubSubItems });
}
}
});
if (newSubItem?.length > 0) {
listOfItems.push({ ...item, children: newSubItem });
}
}
});
return listOfItems;
};
Sample of arr received as parameter in the fnc:
const list = [
{
id: "41",
title: "sample",
children: [
{
id: "42",
title: "sample",
children: [
{
id: "43",
title: "sample",
children: [],
},
{
id: "44",
title: "sample",
children: [],
},
{
id: "45",
title: "sample",
children: [],
},
],
},
{
id: "46",
title: "sample",
children: [
{
id: "47",
title: "sample",
children: [],
},
{
id: "48",
title: "sample",
children: [],
},
],
},
],
},
{
id: "29",
title: "sample",
children: [
{
id: "30",
title: "sample",
children: [],
},
{
id: "49",
title: "sample",
children: [],
},
{
id: "31",
title: "sample",
children: [],
},
],
},
];
If you really don't want to change your structure and flatten your list, you can loop them dynamically, just use a function and call it within itself.
Here's an example using the const list you provided:
let found = false,
loop = (items, filter) => {
found = false;
let results = items.filter(a => filter(a));
if(results.length > 0) {
found = true;
return results;
}
if(!found && items && items.length > 0) {
for(let i = 0; i < items.length && !found; i++) {
if(items[i] && items[i].children && items[i].children.length > 0)
results = loop(items[i].children, filter);
}
}
return results;
};
let items = loop(list, item => item.id && item.id == "48");
In the example above we filter the list dynamically, iterating each and every item in the list and return the first item we find that matches a provided filter (2nd parameter).
You can use this to do pretty much whatever you'd like and can add arguments to add the menu "level" you're currently on, the parents, etc.
Note that this might get a bit slow if the list goes very deep, wrote it quickly and didn't tested outside of your provided list.
Ideally I would change the structure in order to make it easier to work with but if you must keep it this way, this works.
You could find the object (without children) and get a flat array.
const
find = (array, title) => array.flatMap(({ children, ...o }) => [
...(o.title.includes(title) ? [o]: []),
...find(children, title)
]),
list = [{ id: "41", title: "sample", children: [{ id: "42", title: "sample", children: [{ id: "43", title: "sample", children: [] }, { id: "44", title: "sample", children: [] }, { id: "45", title: "sample", children: [] }] }, { id: "46", title: "no sample", children: [{ id: "47", title: "sample", children: [] }, { id: "48", title: "sample", children: [] }] }] }, { id: "29", title: "sample", children: [{ id: "30", title: "sample", children: [] }, { id: "49", title: "no sample", children: [] }, { id: "31", title: "sample", children: [] }] }];
console.log(find(list, 'no sample'));
console.log(find(list, 'ample'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have this JSON structure:
const arr = [
{
id: "TaskStatuses",
rows: [
{id: "1", name: "Success"},
{id: "2", name: "Error"},
]
},
{
id: "Objects",
rows: [
{id: "1", name: "Object1"},
{id: "2", name: "Object2"},
]
},
{
id: "Groups",
rows: [
{id: "1", name: "Group1"},
{id: "2", name: "Group2"},
]
},
]
I need to create array with some condition. If my condition correctly I will push elements arr.rows.
Finally i want to get this structure:
[
{
Objects: "Object1",
Groups: "Group1"
},
{
Objects: "Object2",
Groups: "Group2"
}
]
I try to do like this
let sites = []
for (let el in arr) {
if (arr.id == "Objects") {
for (let item of el.rows) {
sites.push({Objects: item.name})
}
}
if (arr.id == "Groups") {
for (let item of el.rows) {
sites.Groups = item.name
}
}
}
You could map the wanted properties in new objects.
const
data = [{ id: "TaskStatuses", rows: [{ id: "1", name: "Success" }, { id: "2", name: "Error" }] }, { id: "Objects", rows: [{ id: "1", name: "Object1" }, { id: "2", name: "Object2" }] }, { id: "Groups", rows: [{ id: "1", name: "Group1" }, { id: "2", name: "Group2" }] }],
ids = ['Objects', 'Groups'],
result = data.reduce((r, { id, rows }) => ids.includes(id)
? rows.map(({ name }, i) => ({ ...r[i], [id]: name }))
: r,
[]
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
my variable look like this
var widgets2 = [{
id: "1",
title: 'title',
children: [],
},
{
id: "2",
title: 'title2',
children: []
},
{
id: "3",
title: 'title3',
children: [{
id: "4",
title: 'title4',
children: [],
}, {
id: "5",
title: 'title5',
children: [],
children: [{
id: "6",
title: 'title6',
children: [],
},
{
id: "7",
title: 'title7',
children: [],
}
]
}],
},
{
id: "9",
title: 'title9',
children: [],
}
]
The function code look like this
function findTheKey(id,widget){
let newObj=[...widget];
for(var key in newObj){
if(newObj[key]['id']==id){
console.log(newObj[key])
return newObj[key];
}
console.log("came here")
if(newObj[key].hasOwnProperty("children")){
findTheKey(id,newObj[key].children);
}
}
}
When the called the function using following code
var result=findTheKey(4,widgets2);
console.log(result)
The result look like this
{id: "4", title: "title4", children: Array(0)}
came here
That means even after executing return statement, console.log getting executed, any help will be highly appreciated.
Thankyou
Since this is a recursive function, the return does not have the effect you expect, you need a variable outside the recursive function to keep the current state of what you want to find.
See the snippet below for example:
var widgets2 = [
{
id: "1",
title: "title",
children: [],
},
{
id: "2",
title: "title2",
children: [],
},
{
id: "3",
title: "title3",
children: [
{
id: "4",
title: "title4",
children: [],
},
{
id: "5",
title: "title5",
children: [],
children: [
{
id: "6",
title: "title6",
children: [],
},
{
id: "7",
title: "title7",
children: [],
},
],
},
],
},
{
id: "9",
title: "title9",
children: [],
},
];
let found;
function findTheKey(id, widget) {
let newObj = [...widget];
for (var key in newObj) {
if (newObj[key]["id"] == id) {
found = newObj[key];
break;
}
if (newObj[key].hasOwnProperty("children")) {
findTheKey(id, newObj[key].children);
}
}
return found;
}
var result = findTheKey(4, widgets2);
console.log(result);
You should update your question for this, but since you asked for it in the comments, here is what I propose.
function findTheKey(id, widget) {
const newObj = [...widget];
for (const key in newObj) {
if (newObj[key]["id"] === `${id}`) {
return newObj[key];
}
if (newObj[key].hasOwnProperty('children')) {
/* Use the result of the recursive function */
const foundObject = findTheKey(id, newObj[key].children);
if(foundObject) return foundObject;
}
}
return false;
}
The need is to take objects like this:
[ { "first":
{ "children" : [{ "name": "abc", "detail":"123"},
{ "name": "def", "detail":"456"}
]
}},
{ "second":
{ "children" : [{ "name": "ghi", "detail":"123"},
{ "name": "jkl", "detail":"456"}
]
}},
{ "third":
{ "children" : [{ "name": "mno", "detail":"123"},
{ "name": "pqr", "detail":"456"}
]
}},
{ "fourth":
{ "children" : [{ "name": "stu", "detail":"123"},
{ "name": "vwx", "detail":"456"}
]
}},
{ "fifth":
{ "children" : [{ "name": "yz", "detail":"123"},
{ "name": "abc", "detail":"456"}
]
}},
{ "sixth":
{ "children" : [{ "name": "def", "detail":"123"},
{ "name": "ghi", "detail":"456"}
]
}}
]
and then create a flattened array of unique values (options for a select) from the name field of the children that looks like this:
[{"value":"abc", "label":"abc"},
{"value":"def", "label":"def"},
{"value":"ghi", "label":"ghi"},
{"value":"jkl", "label":"jkl"},
{"value":"mno", "label":"mno"},
{"value":"pqr", "label":"pqr"},
{"value":"stu", "label":"stu"},
{"value":"vwx", "label":"vwx"},
{"value":"yz", "label":"yz"}
]
The code below is working, but it looks like it is inefficient because it appears to make many passes over the array:
[
...new Set(
[].concat.apply([], bases.map((base) => {
if (!base.children || base.children.length === 0) return;
return base.children}
)).map((child) => child.name)
)
].map((optName) => {return {value: optName, label: optName};})
If it is possible, how can this same result be achieved without as many iterations across the array.
Firstly, as a rule of thumb, you shouldn't worry too much about performance until you have a reason to do so.
Secondly, chaining the array prototype functions (e.g. map, forEach, filter) will require multiple iterations by design.
Thirdly, there's no reason to assume multiple iterations is slower than a single iteration if the work done within the iterations is the same anyways. I.e. incrementing an index and comparing it with an array length isn't going to be the bottleneck compared to pushing objects into arrays and check set entries.
Here's a (IMO) cleaner snippet to extract unique names from your array:
let bases = [{
children: [{
name: "abc",
detail: "123"
},
{
name: "def",
detail: "456"
}
]
}, {
children: [{
name: "abc" ,
detail: "123"
},
{
name: "xyz" ,
detail: "456"
}
]
},
{}
];
let output = bases
.flatMap(b => b.children || [])
.map(c => c.name)
.filter((v, i, a) => a.indexOf(v) === i) // filter unique values
.map(name => ({
value: name,
label: name,
}));
console.log(output);
Now if you really want to do all this in a single iteration, that too is possible, but harder to read:
let bases = [{
children: [{
name: "abc",
detail: "123"
},
{
name: "def",
detail: "456"
}
]
}, {
children: [{
name: "abc" ,
detail: "123"
},
{
name: "xyz" ,
detail: "456"
}
]
},
{}
];
let output = [];
let seenNames = {};
for (base of bases) {
if (!base.children)
continue;
for (child of base.children) {
let name = child.name;
if (seenNames[name])
continue;
seenNames[name] = true;
output.push({
value: name,
label: name,
});
}
}
console.log(output);
You could take Array#flatMap for getting a flat representation of data for using unique values and map new objects.
var data = [{ first: { children: [{ name: "abc", detail: "123" }, { name: "def", detail: "456" }] } }, { second: { children: [{ name: "ghi", detail: "123" }, { name: "jkl", detail: "456" }] } }, { third: { children: [{ name: "mno", detail: "123" }, { name: "pqr", detail: "456" }] } }, { fourth: { children: [{ name: "stu", detail: "123" }, { name: "vwx", detail: "456" }] } }, { fifth: { children: [{ name: "yz", detail: "123" }, { name: "abc", detail: "456" }] } }, { sixth: { children: [{ name: "def", detail: "123" }, { name: "ghi", detail: "456" }] } }],
result = Array.from(
new Set(data
.flatMap(Object.values)
.flatMap(({ children }) => children.map(({ name }) => name))
),
value => ({ value, label: value })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have two arrays details and options that coming from different sources (web api requests)
details: [
{ id: 'groups', option: true },
{ id: 'category', option: false }
]
options: {
groups: [
{ id: 'g1' },
{ id: 'g2' }
],
category: [
{ id: 'c1' },
{ id: 'c2' }
],
other: [
{ id: 'o1' },
{ id: 'o2' }
],
}
I want to combine these tow arrays like
combined: [
groups:
{
options:[
{ id: 'g1' },
{ id: 'g2' }
],
details: { option: true}
},
category:
{
options: [
{ id: 'c1' },
{ id: 'c2' }
],
details: { option: false}
},
]
Basically if any id from details is matching to options property it should go in to new array under the same property name and all details except id goes to related details property.
What is the best way of doing that? Is lodash can handle that ?
If you only want the items in both options and details (intersection):
var details = [
{ id: 'groups', option: true },
{ id: 'category', option: false }
]
var options = {
groups: [
{ id: 'g1' },
{ id: 'g2' }
],
category: [
{ id: 'c1' },
{ id: 'c2' }
],
other: [
{ id: 'o1' },
{ id: 'o2' }
]
}
var combined = {};
details.forEach(({id: id, option: option}) => {
if (options[id]) {
combined[id] = combined[id] || {};
combined[id].options = options[id];
combined[id].details = {option: option};
}
})
console.log(JSON.stringify(combined, null, "\t"))
/*
{
"groups": {
"options": [
{
"id": "g1"
},
{
"id": "g2"
}
],
"details": {
"option": true
}
},
"category": {
"options": [
{
"id": "c1"
},
{
"id": "c2"
}
],
"details": {
"option": false
}
}
}
*/
If you want to retain all items from options and details whether or not they match (union):
var details = [
{ id: 'groups', option: true },
{ id: 'category', option: false }
]
var options = {
groups: [
{ id: 'g1' },
{ id: 'g2' }
],
category: [
{ id: 'c1' },
{ id: 'c2' }
],
other: [
{ id: 'o1' },
{ id: 'o2' }
]
}
var combined = {};
Object.keys(options).forEach(id => {
combined[id] = {};
combined[id].options = options[id];
})
details.forEach(({id: id, option: option}) => {
combined[id] = combined[id] || {};
combined[id].details = {option: option};
})
console.log(JSON.stringify(combined, null, "\t"))
/*
{
"groups": {
"options": [
{
"id": "g1"
},
{
"id": "g2"
}
],
"details": {
"option": true
}
},
"category": {
"options": [
{
"id": "c1"
},
{
"id": "c2"
}
],
"details": {
"option": false
}
},
"other": {
"options": [
{
"id": "o1"
},
{
"id": "o2"
}
]
}
}
*/
You need to use [] notation to push details values.
options['groups']['details'] = true
options['groups']['category'] = false
Here's the solution for your problem
var details= [
{ id: 'groups', option: true },
{ id: 'category', option: false }
];
var options= {
groups: [
{ id: 'g1' },
{ id: 'g2' }
],
category: [
{ id: 'c1' },
{ id: 'c2' }
],
other: [
{ id: 'o1' },
{ id: 'o2' }
],
};
var combined = {};
for (var i= 0;i<details.length;i++){
var obj = {}
obj.options=options[details[i].id];
obj.details = {};
obj.details.option=details[i].option;
combined[details[i].id] = obj;
}
console.log(combined)