Grouped array gets empty (Lodash's groupBy does work though) - javascript

Here is the sample plunker http://embed.plnkr.co/bJFmT0WlRfqUgrCxZRT6
To start with: I am grouping a collection by a certain key - in my example yob.
I have two options -
I can write a custom function that does the job (which I want to do as I can add custom logic)
I can use _.groupBy provided by lodash/underscore.js
So I decided to try both the methods - using lodash I group the collection by a key and output is displayed (see plunkr)
When I use the custom method, studentsByYear in this case, somehow the array becomes empty before being displayed. I have console logged my output before returning the array and the array has the desired output ..
So my question is why my grouping method does not work? Am I missing something obvious in angular? Is it that I have to do a deep copy of objects before I return them, if yes please explain?
<div ng-controller="myController">
<h2> Using Lodash </h2>
<ul ng-repeat="(yob, students) in myModel.studentsByYobLodash">
<h3>{{ yob }}</h3>
<div ng-repeat="s in students">
<p> {{s.name}} </p>
</div>
</ul>
<h2>Not using Lodash </h2>
<ul ng-repeat="(yob, students) in myModel.studentsByYob">
<h3>{{ yob }}</h3>
<div ng-repeat="s in students">
<p> {{s.name}} </p>
</div>
</ul>
</div>
script
var app = angular.module("myApp", []);
app.factory('studentsFactory', [function () {
var students = [{
name: 'Tony',
yob: '1987'
},{
name: 'Rachel',
yob: '1988'
}, {
name: 'Eric',
yob: '1988'
}, {
name: 'Jon',
yob: '1988'
}, {
name: 'Tim',
yob: '1989'
}, {
name: 'Bing',
yob: '1987'
}, {
name: 'Valerie',
yob: '1988'
}, {
name: 'Brandon',
yob: '1987'
}, {
name: 'Sam',
yob: '1987'
}]
return {
getStudents: function () {
return students;
}
}
}])
app.controller('myController', ['$scope', 'studentsFactory', function ($scope, studentsFactory) {
$scope.myModel = [];
$scope.myModel.students = studentsFactory.getStudents();
$scope.myModel.studentsByYobLodash = studentsByYearUsingLodash($scope.myModel.students)
$scope.myModel.studentsByYob = studentsByYear($scope.myModel.students);
function studentsByYearUsingLodash (students) {
return _.groupBy(students, 'yob');
}
function studentsByYear(students) {
var arr = [];
angular.forEach(students, function (student) {
var key = student.yob;
_.has(arr, key) ? arr[key].push(student) : (arr[key] = [student]);
})
return arr;
}
}])

With the structure you have you are using key, value with object iteration in ng-repeat. So by sending myModel.studentsByYob as an array will eventually return an array with holes, because you will end up having, for instance, myModel.studentsByYob[0..] as undefined as they don't exist and the array object instead has property 1987, 1988 etc which points to the array of students and if you check the browser console you will see the exact same error pointed by the ng-repeat code because of multiple undefined keys returned. So just change:
var arr = [];
to
var arr = {};
Plunkr

The issue is when you create the arr in studentsByYear():
var arr = [];
should be
var arr = {};
Angular iterators treat arrays and objects differently, so when iterating on a non-zero-indexed array, using (key, value) will always result in an unset key. Since Angular thinks undefined == undefined, it results in a duplicate key error.
Incidentally: you theoretically could get away with this error exactly once, so if your yob's were:
[1, 2, 3, 4...] instead of [1987, ...]
you would not have had an error, just an empty "0" at the top of your list.
http://plnkr.co/edit/VPiJSjOqPNFeunc7LUqJ?p=preview
But once you have 2 out-of-sequence indices
[2, 3, 4...] // 0 and 1 are missing
then you will again get the error, because 0 == undefined and 1 == undefined, therefore 0 == 1 and it's a duplicate error.

Related

What is the best way to delete objects from Array

I am wondering if what is the best way to find all Indexes of objects in an Array and then delete them from the Array.
Currently, my code looks like as below;
var data = _.find(group.value, function(valueItem){ return valueItem.value == filter.value });
var index =_.indexOf(group.value,data);
group.value.splice(index,1);
This works great, but only gets me the first index if the index is more than once. So, I am looking for a method that will get me all indexes in my Array, so I can loop through in remove all
Use filter to create a new array of objects.
Based on your example this code creates new arrays based on the value (name) of each value property. I've wrapped the filter code in a function (getData) that you can call with the original array and the name you want to check as arguments.
If there is no match the function will return an empty array.
const arr = [
{ id: 1, value: 'bob' },
{ id: 2, value: 'dave' },
{ id: 3, value: 'bob' },
{ id: 4, value: 'bob' }
];
function getData(arr, name) {
return arr.filter(obj => obj.value === name);
}
console.log(getData(arr, 'bob'));
console.log(getData(arr, 'dave'));
console.log(getData(arr, 'mary'));

How to iterate one property over more than one JSON Arrays in response array

I have response array like below comes fromm HTTP Get Request
I want to print all these name properties of any object using *ngFor.Here I tried below but didn't work.
<ng-container *ngFor="let index of filteredNames">
<h4>{{index.name}}</h4>
</ng-container>
FilteredNames have objects as you can see on the image taken by console
This will depend on how you want to show it in the view.
You have an array of arrays. If you want to loop through all values in the second array, you could have:
<div *ngFor="let filteredName of filteredNames">
<!-- this will show all the values in the second array -->
<div *ngFor="let value of filteredName">{{ value.name }}</div>
</div>
here is the same example look into. hope it will help you.
I have give flatten example using js in angular.
In component
filteredNames = [];
ArrayFromBackResponse = [
[
{
id: 1,
name: "test",
hospitalId: 2
},
{
id: 2,
name: "test2",
hospitalId: 2
}
],
[
{
id: 9,
name: "test25",
hospitalId: 2
}
]
];
constructor() {
this.filteredNames = [].concat.apply([],this.ArrayFromBackResponse);
}
In html
<p *ngFor="let o of filteredNames">
{{o.name}}
</p>
we can also flatten array using lodash(_ underscore.js) lib..
filteredNames = _.flatten(this.ArrayFromBackResponse);
Thanks,
kushal

How to delete a object in a list? Javascript nodejs and underscore

Hello I am new to Javascript and NodeJS, but I need to delete a object in a array with a boolean in the list. There are 3 friends in the list and 2 friends with isEvil = true; So i need to delete 2 friends and the output must be 1.
This is what i tried.
MyTry 1:
_.each(user.friends, function(friend) {
if(friend.isEvil){
delete friend;
}
});
console.log(user.friends.length); //output is 3
If I do this it will delete all the properties but there is still a empty object there: MyTry 2:
_.each(user.friends, function(friend) {
if(friend.isEvil){
delete f.property1;
delete f.property2;
}
});
console.log(user.friends.length); //output is 3
And the last one i tried is:
_.each(user.friends, function(friend, key) {
if(friend.isEvil){
delete user.friends[key];
}
});
console.log(user.friends.length); //output is 3
just use the _.filter(list, predicate, [context]) function provided with underscore:
_.filter(user.friends, function(friend){
return !friend.isEvil;
});
for more info: http://underscorejs.org/#filter
Filter the users friend like
user.friends.filter(function (friend) {
return !friend.isEvil;
});
var frieds = [{ name: 1, isEvil: false }, { name: 2, isEvil: true }, { name: 3, isEvil: true }];
var notEvil = friends.filter(function (friend) {
return !friend.isEvil;
});
console.log(notEvil);
To get rid of evil friends you could use underscore's reject function:
var niceFriends = _.reject(friends, 'isEvil');
var friends = [
{ id: 'mickey', isEvil: false },
{ id: 'donald', isEvil: true },
{ id: 'minnie', isEvil: false }
];
var niceFriends = _.reject(friends, 'isEvil');
document.getElementById('result').textContent = JSON.stringify(niceFriends);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.2/underscore.js"></script>
<p>
<pre id="result"></pre>
</p>
Delete operator leaves your array with undefined on the place of removed element (see delete). I believe you should use Splice method to remove elements.
Or you might just use built in Underscores methods to filter your array (e.g. filter), like:
user.friends = _.filter(user.friends, function(item) {
return !item.isEvil;
};
try this,basically you iterate though you array and splice all unwanted objects:
for(var i in user.friends){
if(user.friends[i].isEvil){
user.splice(i,1)
}
}

underscore js - finding a nested object

I have an underscore filter which is returning the parent object which contains a child object I am looking for. But I want it to return just the child object. Since it is already doing the work of locating the child object in order to return the parent, I'm wondering how to simplify my code to return just the child. Here's the example:
var filterObj = _.filter(filtersPath, function(obj) {
return _.where(obj.filters, {id: prefilterCat}).length > 0;
});
So here, that nested object inside obj.filters, with the id of prefilterCat, is the object I want returned, not its parent. So currently I would have to do another find inside of filterObject to get what I need. Any ideas?
Underscore's filter method will return the "parent" object but will filter out the ones that don't match the conditional statement. That being the case, if there is only 1 result, then you can just access it similarly to how you would access an array. For instance:
var filterObj = _.filter(filtersPath, function(obj) {
return _.where(obj.filters, {id: prefilterCat}).length > 0;
})[0];
The above example would get the first child that is returned from the filter method.
From your question and code, I'm assuming a data structure like this:
var filtersPath = [
{
filters: [
{id: 0},
{id: 1}
]
},
{
filters: [
{id: 5},
{id: 42}
]
}
];
Now you can get an array of all "parent objects" (which you already have done) that have a filters array containing a object with matching ID:
_.filter(filtersPath, function(obj) {
return _.find(obj.filters, { id: 5 });
});
The advantage of doing it this way is that it will stop searching for a value once it's found one, and not always traverse the entire list.
If you want to actually get an array as result, it's a simple map operation:
_.chain(filtersPath)
.filter(function(obj) {
return _.find(obj.filters, { id: 5 });
})
.map(function(obj) {
return obj.filters;
})
.value();
If you only want to get the first matching object, you don't even need to use a filter or map:
_.find(filtersPath, function(obj) {
return _.find(obj.filters, { id: 5 });
})
.filters;
With lo-dash, this operation will be a little easier:
_.find(filtersPath, { filters: [{ id: 5 }] }).filters

Observable objects inside observableArray do not automatically update [duplicate]

This question already has answers here:
Knockout JS - How to correctly bind an observableArray
(2 answers)
Closed 9 years ago.
I have been looking into an Knockout for dynamic data-bind and I have a situation where I need an observable array to contain multiple observable objects.
This is my code:
<ul data-bind="foreach: { data: categories, as: 'category' }">
<li>
<ul data-bind="foreach: { data: items, as: 'item' }">
<li>
<span data-bind="text: category.name"></span>:
<span data-bind="text: item"></span>
<input type="text" data-bind="value: item"/>
</li>
</ul>
</li>
</ul>
$(document).ready(function () {
ko.applyBindings(viewModel);
});
var viewModel = {
categories: ko.observableArray([
{ name: 'Fruit', items: [ko.observable('Apple'), ko.observable('Orange'), ko.observable('Banana')] },
{ name: 'Vegetables', items: [ko.observable('Celery'), ko.observable('Corn'), ko.observable('Spinach')] }
])
};
When working with oject observables usually I could modify a value of an input text box and that value is set to the entire page where that property was used to be displayed.
In my current example I tried to do the same with my input box , but after I modified the values in the text box the span did not to the curent value.
How can I make the observable objects inside the observableArray behave as they would have if they were stand alone observable objects?
when i encounter these issues, i like to break them down into sub vms, which allow me better control over what is happening at each level of context that im in. for your issues above, i would do something like this:
var produceVM = function (data) {
var self = this;
self.item = ko.observable(data);
}
var categoryVM = function (data) {
var self = this;
self.name = ko.observable(data.name);
self.items = ko.observableArray();
var items = ko.utils.arrayMap(data.items, function (item) {
return new produceVM(item);
});
self.items(items);
}
var viewModel = function (data) {
var self = this;
self.categories = ko.observableArray();
var categories = ko.utils.arrayMap(data, function (category) {
return new categoryVM(category);
});
self.categories(categories);
}
var data = [
{ name: 'Fruit', items: [ 'Apple', 'Orange', 'Banana' ] },
{ name: 'Vegetables', items: ['Celery', 'Corn', 'Spinach' ]}
];
ko.applyBindings(new viewModel(data));
I believe the ko mapping plugin achieves something similar to this without having to write all of the above code. you could pass it the data model and it would construct the observables for each item.
As #nemesv pointed, answer lies right under the corner.
Simply wrap every array item into object and it will work flawlessly.
This is how your view model should look, and here is a working jsfiddle
var viewModel = {
categories: ko.observableArray([{
name: 'Fruit',
items: [
{name: ko.observable('Apple')},
{name: ko.observable('Orange')},
{name: ko.observable('Banana')}
]
}, {
name: 'Vegetables',
items: [
{name: ko.observable('Celery')},
{name: ko.observable('Corn')},
{name: ko.observable('Spinach')}
]
}])
};
I have to say from my own experience, that usually you'll have objects inside array anyway, not claiming that there isn't other use cases, just saying that it is very useful to have objects in array and have ability to change them dynamically and not to worry about anything else.

Categories

Resources