Angularjs map array to another array - javascript

I have two arrays, Users and Employments like so:
Users = [{id:1, name: "ryan"}, {id:2, name:"Julie"}]
Employments = [{user_id: 1, title: "manager"}, {user_id: 2, title: "Professor"}]
I'd like to display the Employments array in an ng-repeat like so:
<li ng-repeat="employment in Employments">
{{employment.user.name}}
</li>
How do I map the Users array to the Employments array?

If you want the employee name to get displayed based on id, the simplest way is just pass that id to a function and return the name, like as shown below
Working Demo
html
<div ng-app='myApp' ng-controller="ArrayController">
<li ng-repeat="employment in Employments">{{getEmployeeName(employment.user_id)}}
</li>
</div>
script
var app = angular.module('myApp', []);
app.controller('ArrayController', function ($scope) {
$scope.Users = [{
id: 1,
name: "ryan"
}, {
id: 2,
name: "Julie"
}];
$scope.Employments = [{
user_id: 1,
title: "manager"
}, {
user_id: 2,
title: "Professor"
}];
$scope.getEmployeeName = function (empId) {
for (var i = 0; i < $scope.Users.length; i++) {
if ($scope.Users[i].id === empId) {
return $scope.Users[i].name;
}
};
};
});
UPDATE 2
If you want to embed the User array in the Employments array, try the following stuff
$scope.Users = [{id: 1, name: "ryan"}, {id: 2, name: "Julie"}];
$scope.Employments = [{user_id: 1, title: "manager"},
{user_id: 2, title: "Professor"}
];
code for flattening Employments array by adding User properties
angular.forEach($scope.Users, function (user, userIndex) {
angular.forEach($scope.Employments, function (employee, employeeIndex) {
if (employee.user_id === user.id) {
employee.name = user.name;
}
});
});
Output
$scope.Employments = [ { user_id: 1, title: "manager", name: "ryan" },
{ user_id: 2, title: "Professor", name: "Julie" }
]
Working Demo
UPDATE 3
Code for making a nested employee structure like as shown below from $scope.Users and $scope.Employments
$scope.employees = [];
angular.forEach($scope.Employments, function (employee, employeeIndex) {
var employeeObject = {};
employeeObject.title = employee.title;
angular.forEach($scope.Users, function (user, userIndex) {
if (employee.user_id === user.id) {
employeeObject.user = user;
}
});
$scope.employees.push(employeeObject);
});
Output
[ { title: "manager", user: { "id": 1, "name": "ryan" } },
{ title: "Professor", user: { "id": 2, "name": "Julie" } }
]
Working Demo

If you wanted to match up the two following arrays purely with a template you could take the following arrays
Users = [{id:1, name: "ryan"}, {id:2, name:"Julie"}]
Employments = [{user_id: 1, title: "manager"}, {user_id: 2, title: "Professor"}]
And nest a repeat like:
<li ng-repeat="employment in Employments">
<div ng-repeat="user in Users" ng-if="user.id === employment.user_id" >
{{user.name}}:{{employment.title}}
</div>
</li>
Two more nice little thing to do to avoid any risk of getting those brackets showing on a slow page load is to use the ng-bind and prefix the attributes with data so its with the html spec
<li data-ng-repeat="employment in Employments">
<div data-ng-repeat="user in Users" data-ng-if="user.id === employment.user_id" >
<span data-ng-bind="user.name"></span>:<span data-ng-bind="employment.title"></span>
</div>
</li>
I know you didn't have the need for anything but the name, but figured a quick example of using the outer loop in the inner still could be helpful. Also this would be the case for ng-init if you needed to reference the the $index of the outer ng-repeat from the inner, but that might be more than you're looking for here.

Plunker
This sorts the users names into the employments array:
var sortUsers = function() {
var i = 0;
for (i; i < $scope.users.length; i++) {
console.log($scope.users[i].id)
for(var z = 0; z < $scope.employments.length; z++) {
if($scope.employments[z].user_id === $scope.users[i].id) {
$scope.employments[z].name = $scope.users[i].name;
}
}
}
}
HTML:
<ul>
<li ng-repeat="employment in employments">
{{employment.name}}
</li>
</ul>

I dealt similar problem yesterday. If you want to use js, have to loop twice.
I recommend to use the best way is to select in one query by join table if data come from single database.
You select User by one query, and Employment for another query in database. Then, twice ng-repeat to re-arrange. Here is my solution.
select users.*, employments.title from `users` inner join `employments` where users.id = employments.user_id;
Hope be be helpful.

Related

Node Js how to fetch data from database in an hierarchical way

I'm writing a back code using NodeJs to fetch some data from backend, I want dataBase data to be like this
like this:
data = [{
name: "Admin",
id: '1',
children: [
{ name: "Admin", id: "1" },
{ name: "groupe1", id: "2" },
{
name: "groupe2", id: "1455", children: [
{ name: "groupe2", id: "1455" },
{ name: "gro", id: "5444" },
{ name: "hhrr", id: "45" }
]
}
]
}]
the idea is simple we have a list of group each group has a parent I want to display all the groups list in an hierarchical way the top one of the tree is done
Some groups are parents and groups in the same time and some others are only groups if the group is not parent we add an object with its name and ID in the array of children of his parent
if this groups is a parent that's mean it has children we add an object with its ID and name in the array of children of his parents, and we add property children for the object which is array named children with for the first time an object with the name and the id of the group etc...
i tryed to do this but it did not work
const getParentsByType = async ({ name, _id }) => {
let parentResult = [
{
id: _id,
name: name,
children: [
{
id: _id,
name: name,
},
],
},
];
parentResult= await findParent(_id, parentResult[0].children, 0);
return parentResult;
};
const findParent = async (parentId, parentResult, itemPos) => {
let children = await Models.GroupModel.find({ parent: parentId, status: true }).select('name _id');
for (let i = 0; i < children.length; i++) {
let childrenList = await Models.GroupModel.find({ parent: children[i]._id, status: true }).select('name _id');
if (childrenList.length != 0) {
parentResult.push(buildParentWithChild(children[i]._id, children[i].name));
findParent(children[i]._id,parentResult.children[i],itemPos++)
} else {
parentResult.push(buildParent(children[i]._id, children[i].name));
}
}
return parentResult
};
and this the model of the data base
const Group = mongoose.Schema({
name: {
type: String,
required: true,
},
status: {
type: Boolean,
required: true,
},
parent: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Group',
},
});
i had two days trying to resolve tis but with no result
i need some helps and Thank you
Try parsing your returned data. It validates your data as objects i dont see any problem with your function regardless i still have no idea what format your a trying to build.
let children = JSON.parse(JSON.stringify(await Models.GroupModel.find({ parent: parentId, status: true }).select('name _id')));
let childrenList = JSON.parse(JSON.stringify(await Models.GroupModel.find({ parent: children[i]._id, status: true }).select('name _id')));
If I understand you right, you want to convert the array returned by Models.GroupModel.find, and which looks like
var dbresult = [
{_id: "1", parent: null, name: "one"},
{_id: "2", parent: "1", name: "two"}
];
into a hierarchical structure. This can be done with a function that adds all children of a given parent p, including, recursively, their children. Like the following:
function children(p) {
var result = [];
for (r of dbresult) if (r.parent === p) {
var row = {_id: r._id, name: r.name};
var chld = children(r._id);
if (chld.length > 0) row.children = chld;
result.push(row);
}
return result;
}
console.log(JSON.stringify(children(null)));
Note that this approach requires only one database access (to fill the dbresult) and is therefore probably faster than your findParent function.

how to loop through multiple arrays inside an array and filter a value from it-Javascript

I'm using EXTJS framework for my code.
below is my array structure:
data = [{
id: 22,
rows: [{
id: "673627",
name: "ABS",
address: "536street"
}, {
id: "333",
name: "TEST$$",
address: "536street"
}, {
id: "999",
name: "TEST$$",
address: "536street"
}]
}, {
id: 33,
rows: [{
id: "899",
name: "TES",
address: "536street"
}, {
id: "333",
name: "TEST$$",
address: "536street"
}, {
id: "999",
name: "TES673",
address: "536street"
}]
}]
Now I want to filter the name from this array, whose value I'm comparing with say "TEST$$".
I'm doing this;
Ext.each(data, function(item) {
filter = item.rows.filter(function(name) {
return name.name === "TEST$$";
}, this);
}, this);
console.log(filter);
In this case, it returns only 1 match, where as I have 3 matches for this particular value. It returns the match from the last item in the data array and hence I dont get all the matching values, any idea how this can be looped to get all values matching?
thx!
You're reassigning the filter variable on every iteration over the data array:
filter = item.rows.filter(function(name) {
return name.name === "TEST$$";
}, this);
On the last iteration, there is only one match, the one with id of 333, so that's the only one that you see after running the Ext.each. Try pushing to an external array that doesn't get overwritten instead:
const testItems = [];
Ext.each(data, function(item) {
const filtered = item.rows.filter(row => row.name === "TEST$$")
testItems.push(...filtered);
});
console.log(testItems);
Note that there's no need to pass along the this context.
Another option is to flatMap to extract all rows to a single array first:
const output = data
.flatMap(({ rows }) => rows)
.filter(({ name }) => name === 'TEST$$');

How to build tree array from flat array of object with category and subCategrie properties

I am trying to build tree array from flat array, each item in the flat array has two property need to be used to build the tree array, they are 1. category. 2. subCategrie which is array of string.
let data = [
{
id: 1,
name: "Zend",
category: "php",
subCategory: ["framework"]
},
{
id: 2,
name: "Laravel",
category: "php",
subCategory: ["framework"]
},
{
id: 3,
name: "Vesion 5",
category: "php",
subCategory: ["versions"]
},
{
id: 4,
name: "Angular",
category: "frontend",
subCategory: ["framework", "typescript"]
},
{
id: 5,
name: "Aurelia",
category: "frontend",
subCategory: ["framework", "typescript"]
},
{
id: 6,
name: "JQuery",
category: "frontend",
subCategory: []
}
];
It should be
let tree = [
{
name: "php",
children: [
{
name: "framework",
children: [
{
id: 1,
name: "Zend"
},
{
id: 2,
name: "Laravel"
}
]
},
{
name: "versions",
children: [
{
id: 3,
name: "Vesion 5"
}
]
}
]
}
// ...
];
Is there any article, link solving similar problem?
I gave it many tries but stuck when trying to build the sub categories children.
Here's my last attempt which throws error and I know it's wrong but it's for the ones who want to see my attempts
const list = require('./filter.json')
let tree = {};
for (let filter of list) {
if (tree[filter.category]) {
tree[filter.category].push(filter);
} else {
tree[filter.category] = [filter];
}
}
function buildChildren(list, subcategories, category, index) {
let tree = {}
for (let filter of list) {
if (filter.subcategory.length) {
for (let i = 0; i < filter.subcategory.length; i++) {
let branch = list.filter(item => item.subcategory[i] === filter.subcategory[i]);
branch.forEach(item =>{
if (tree[filter.subcategory[i]]){
tree[filter.subcategory[i]] = tree[filter.subcategory[i]].push(item)
}else{
tree[item.subcategory[i]] = [item]
}
})
}
}
}
console.log('tree ', tree);
}
Heads up, For javascript I usually use Lodash (usually written as _ in code) but most of these methods should also be built in to the objects in javascript (i.e. _.forEach = Array.forEach())
const tree = [];
// First Group all elements of the same category (PHP, Frontend, etc.)
data = _.groupBy(data, 'category');
_.forEach(data, function (categoryElements, categoryName) {
// Each Category will have it's own subCategories that we will want to handle
let categorySubCategories = {};
// The categoryElements will be an array of all the objects in a given category (php / frontend / etc..)
categoryElements.map(function (element) {
// For each of these categoryies, we will want to grab the subcategories they belong to
element.subCategory.map(function (subCategoryName) {
// Check if teh category (PHP) already has already started a group of this subcategory,
// else initialize it as an empty list
if (!categorySubCategories[subCategoryName]) { categorySubCategories[subCategoryName] = []; }
// Push this element into the subcategory list
categorySubCategories[subCategoryName].push({id: element.id, name: element.name});
});
});
// Create a category map, which will be a list in the format {name, children}, created from
// our categorySubCategories object, which is in the format {name: children}
let categoryMap = [];
_.forEach(categorySubCategories, function (subCategoryElements, subCategoryName) {
categoryMap.push({name: subCategoryName, children: subCategoryElements});
});
// Now that we've grouped the sub categories, just give the tree it's category name and children
tree.push({name: categoryName, children: categoryMap});
});
};
The key to success here is to create an interim format that allows for easy lookups. Because you work with children arrays, you end up having to use filter and find whenever you add something new, to prevent duplicates and ensure grouping.
By working with a format based on objects and keys, it's much easier to do the grouping.
We can create the groups in a single nested loop, which means we only touch each item once for the main logic. The group has this format:
{ "categoryName": { "subCategoryName": [ { id, name } ] } }
Then, getting to the required { name, children } format is a matter of one more loop over the entries of this tree. In this loop we move from { "categoryName": catData } to { name: "categoryName", children: catData }
Here's an example that shows the two steps separately:
const data=[{id:1,name:"Zend",category:"php",subCategory:["framework"]},{id:2,name:"Laravel",category:"php",subCategory:["framework"]},{id:3,name:"Vesion 5",category:"php",subCategory:["versions"]},{id:4,name:"Angular",category:"frontend",subCategory:["framework","typescript"]},{id:5,name:"Aurelia",category:"frontend",subCategory:["framework","typescript"]},{id:6,name:"JQuery",category:"frontend",subCategory:[]}];
// { category: { subCategory: [ items ] } }
const categoryOverview = data.reduce(
(acc, { id, name, category, subCategory }) => {
// Create a top level group if there isn't one yet
if (!acc[category]) acc[category] = {};
subCategory.forEach(sc => {
// Create an array for this subCat if there isn't one yet
acc[category][sc] = (acc[category][sc] || [])
// and add the current item to it
.concat({ id, name });
});
return acc;
},
{}
)
const nameChildrenMap = Object
.entries(categoryOverview)
// Create top level { name, children } objects
.map(([cat, subCats]) => ({
name: cat,
children: Object
.entries(subCats)
// Create sub level { name, children } objects
.map(([subCat, items]) => ({
name: subCat,
children: items
}))
}))
console.log(nameChildrenMap);

Angular - can I keep two arrays separated and use them in an ng-repeat?

first of all, this is an extension of this question here: Angularjs map array to another array
Currently, I have two scopes:
$scope.products = [
{id: 001, name: "prod 1", ...},
{id: 002, name: "prod 2", ...},
{id: 003, name: "prod 3", ...},
{id: 004, name: "prod 4", ...},
{id: 005, name: "prod 5", ...}
]
$scope.cart = {
products: [001,002]
...
}
$scope.products contains a list of all the available products with the products information, while $scope.cart.products contain a list of IDs added to the cart.
The answer above explains how to merge the two arrays together, however, I'd like to keep them separate and create a sort of map between the two. Is it possible? Is a custom filter in the repeater the best option here, or there's a built-in way to do this in angular? thanks for any suggestion
EDIT
Filter:
filter('mapProducts', function($filter) {
return function(products, ids) {
var result;
result = [];
$filter('filter')(products, function(p) {
if (ids.indexOf(p.id) !== -1) {
return result.push(p);
}
});
return result;
};
});
and in the repeater:
<div ng-repeat="product in products | mapProducts:cart.products">
You don't get much benefit by not creating a new array. You can use an Angular filter to "map" the results, and that might be cleaner than doing the same thing in the controller, but behind the scenes Angular is actually just creating a new sub-array. Even if you make a custom filter, at the end of the day you are creating a new array.
angular.module('myApp').
filter('idNumber', function() {
return function(products,idNumbers) {
var newArray= [];
newArray = products.filter(function(p) {
return idNumbers.find(function(i) {
return i == p.id;
});
})
return newArray;
}
});
Additionally, if you use the view version of the filter:
ng-repeat="product in products | myFilter: cart.products"
There could be a performance hit, depending on how big your array is:
The filter can be applied in the view template with markup like {{ctrl.array | filter:'a'}}, which would do a fulltext search for "a". However, using a filter in a view template will reevaluate the filter on every digest, which can be costly if the array is big.
That means that you would want to use the filter in the controller:
var newArray = $filter($scope.products, 'myFilter', cart.products)
Which also means you would be creating a new array.
You can have $scope.cart.products be an array of references to objects in $scope.products:
$scope.products = [
{id: 001, name: "prod 1", ...},
{id: 002, name: "prod 2", ...},
{id: 003, name: "prod 3", ...},
{id: 004, name: "prod 4", ...},
{id: 005, name: "prod 5", ...}
];
$scope.cart = {
products: []
};
$scope.addProduct = function(product) {
$scope.cart.products.push(product);
}
You can see that they are the same object reference:
$scope.addProduct($scope.products[1]);
// Returns true
$scope.cart.products[0] === $scope.products[1]

orderBy in inner loop on nested ng-repeat

I want to filter websites that are in the filtered categories and display them in order of their rank.The code snippet is:
<div ng-repeat="category in categories | filter:{will return more than one category}">
<div ng-repeat="website in websites | orderBy:'rank'| filter:{ parent_id : category.id }:true">
{{website.title}},{{website.rank}}
</div>
</div>
But in the nested loop, since orderby is in inner loop, it works for each iteration of outer loop but the overall result is not sorted according to rank. Say there are three categories and filter gives cat1 &cat2. If websites with rank 6,2,5 are is cat1 and 9,1 in cat2 then the result will be 2,5,6,1,9.I want the result to be 1,2,5,6,9.How should I do that ?
Should I pass the category in some function and write the js code to get the array of filtered website and then sort them and return them to template or is there any other better way to do that in template itself?
I think what you want to do, can not be done as is. Anyway you could use a custom filter.
New Answer
This approach gives you a category selection mechanism as another example of how you could use this custom filter.
angular.module('app',[])
// categories
.value('categories', [ { id: 0, title:"first" }, { id: 1, title:"second" }, { id: 2, title:"third" } ])
// websites
.value('websites', [ { rank: 3, parent_id: 2, title: "Alice" },
{ rank: 1, parent_id: 1, title: "Bob" },
{ rank: 9, parent_id: 1, title: "Carol" },
{ rank: 2, parent_id: 0, title: "David" },
{ rank: 4, parent_id: 0, title: "Emma" },
{ rank: 5, parent_id: 0, title: "Foo" } ])
// controller,
.controller('ctrl', ['$scope', 'categories', 'websites', function($scope, categories, websites) {
// categories injected
$scope.categories = categories;
// websites injected
$scope.websites = websites;
// categories selection helper, useful for preselection
$scope.selection = { 0: true, 1:false } // 2: false (implicit)
}])
// categories filter, categories injected.
.filter('bycat', ['categories', function(categories) {
// websites is the result of orderBy :'rank', selection helper passed as paramenter.
return function(websites, selection) {
// just an Array.prototype.filter
return websites.filter(function(website) {
// for each category
for(var i=0; i < categories.length; i++) {
var cat = categories[i];
// if category is selected and website belongs to category
if (selection[cat.id] && cat.id == website.parent_id) {
// include this website
return true;
}
}
// exclude this website
return false;
});
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<span ng-repeat="category in categories">
<label class="checkbox" for="{{category.id}}">
<input type="checkbox" ng-model="selection[category.id]" name="group" id="{{category.id}}" />
{{category.title}}
</label>
</span>
<div ng-repeat="website in websites | orderBy:'rank'| bycat: selection">
<p>Rank:{{website.rank}} - {{website.title}} ({{categories[website.parent_id].title}})</p>
</div>
</div>
Old Ansewer
Se code comments.
angular.module('app',[])
// categories will be injected in custom filter.
.value('categories', [ { id: 1, title:"first" }, { id: 2, title:"second" } ])
.controller('ctrl', function($scope) {
// sample websites
$scope.websites = [ { rank: 1, parent_id: 2, title: "Site w/rank 1" },
{ rank: 9, parent_id: 2, title: "Site w/rank 9" },
{ rank: 2, parent_id: 1, title: "Site w/rank 2" },
{ rank: 4, parent_id: 1, title: "Site w/rank 4" },
{ rank: 5, parent_id: 1, title: "Site w/rank 5" } ];
})
// custom filter, categories injected.
.filter('bycat', ['categories', function(categories) {
// websites is the result of orderBy :'rank'
return function(websites, catText) {
// just an Array.prototype.filter
return websites.filter(function(website) {
// if no filter, show all.
if (!catText) return true;
for(var i=0; i < categories.length; i++) {
var cat = categories[i];
// if matches cat.title and id == parent_id, gotcha!
if (cat.title.indexOf(catText) != -1 && cat.id == website.parent_id) {
return true;
}
}
// else were
return false;
});
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<input type="text" ng-model="filterText">
<p>Try "first" and "second"</p>
<div ng-repeat="website in websites | orderBy:'rank'| bycat: filterText ">
{{website.title}},{{website.rank}}
</div>
</div>

Categories

Resources