AngularJS: GroupBy and show items - angularjs

I've got an application where I retreive the information of some objects (into an array). That array would have the following structure:
$scope.items = [
{
id: 23289323,
event: {
id: 972823,
name: 'Event A name',
datetime: '2017-02-01 13:45',
},
player: {
id: 58392,
name: 'Player A name'
},
team: {
id: 38839,
name: 'Team A'
},
datetime: '2017-02-03 22:23'
},
{
id: 482273784,
event: {
id: 972823,
name: 'Event A name',
datetime: '2017-02-01 13:45',
},
player: {
id: 2989273,
name: 'Player B name'
},
team: {
id: 2323434,
name: 'Team B'
},
datetime: '2017-02-03 22:23'
},
{
id: 283273939,
event: {
id: 23092803,
name: 'Event B name',
datetime: '2017-02-01 13:45',
},
player: {
id: 58392,
name: 'Player A name'
},
team: {
id: 38839,
name: 'Team A'
},
datetime: '2017-02-03 22:23'
}
...
]
What I'd like
I'd like to be able to have two lists.
On the left, a list of some customizable groupingBy AngularJS filter. So, I can specify "group it by player" and it shows, on this left list, a list of the players with some information (for example, showing the player's name).
On the right, when I select a specific player, show the items that have this player associated.
What I've tried
<li data-ng-repeat="(key, value) in Ctrl.items | groupBy: 'event.id'">
{{key}}<br/>{{value}}
</li>
What I get
23289323
{id: 23289323,event: {id: 972823,name: 'Event name',datetime: '2017-02-01 13:45',}, player: {id: 58392, name: 'Player name'}, team: { id: 38839,name: 'Team A'}, datetime: '2017-02-03 22:23'}
So, I'm getting the whole item object, but I've not found any way of getting the item that I'm groupBying. Because, right now, if there are 3 items with that event.id I get three <li></li> in stead of only one (the one of the event object).
What I ask
Is there any way of using AngularJS groupBy filter and getting in return the (whole) object that is specifying the grouping?
Remember that the groupBy key can be changed by the user.
If you need any further information, please let me know.
Thank you!

I think I've made it through a custom filter. I'm not sure if it's the best way, so if anyone has another solution, please post it!
This is the code of the filter:
(function(){
angular.module("AppModule").filter('groupByGrouped', function() {
return function(list, groupedElement) {
var result = [];
var used_elements = [];
var ref_item;
var ref_check;
// Loop through every list item
angular.forEach(list, function(item){
// We take the object we want to group by inside the item
ref_item = item[groupedElement];
// If it exists
if(ref_item !== undefined){
if(ref_item.id !== undefined){
// If it has an ID, we take the ID to make it faster.
ref_check = ref_item.id;
}else{
// Otherwise, we identify the specific object by JSON string (slower method)
ref_check = JSON.stringify(ref_item);
}
// If it does not exist yet
if(used_elements.indexOf(ref_check) == -1){
// We add it to the results
result.push(ref_item);
// And we add it to the already used elements so we don't add it twice
used_elements.push(ref_check);
}
}else{
// Otherwise we log it into our console
console.warn("The key '"+groupedElement+"' inside the specified item in this list, does not exist.");
}
});
return result;
};
});
})();
This will return the whole object. So our HTML would be something like:
<ul>
<li data-ng-repeat="(key, obj) in Ctrl.items | groupByGrouped: 'event'">
<span class="object_id">{{obj.id}}</span>
</li>
</ul>
Or even with a directive (not tested, but should work aswell):
<ul>
<li data-ng-repeat="(key, obj) in Ctrl.items | groupByGrouped: 'event'">
<obj_directive ourobject="obj"></obj_directive>
</li>
</ul>

Related

AngularJS ng-repeat and indexOf() to check objects

I have two json objects (data1 and data2) that have related information. Namely, both objects have properties (arrays) which in turn can have identical data. So, I am trying to figure out how to display those data with highlighting them properly, i.e. identical data with green color and non-identical with red color. Somehow it wrongly highlights all data with red color.
Here is the html:
<ul>
<li ng-repeat="item in vm.data2.features"
ng-class="vm.data1.features.indexOf(item) !== -1 ? 'check' : 'uncheck'">
<span ng-bind="item.id"></span>
</li>
</ul>
and objects:
vm.data1 = {
id: '4569',
name: 'Given data',
features: [
{id: "TEST_TEXT2", desc: 'smth12'},
{id: "TEST_PPP", desc: 'smthsmthsmth'},
{id: "TEST_ECASH", desc: "somelongtexthere"}
]
};
vm.data2 = {
id: '1305',
name: 'Base data',
features: [
{id: "TEST_BP", desc: 'smth'},
{id: "TEST_TEXT2", desc: 'smth12'},
{id: "TEST_PPP", desc: 'smthsmthsmth'},
{id: "TEST_TEXT1", desc: 'blahblah'},
{id: "TEST_ECASH", desc: "somelongtexthere"}
]
};
The full demo is here.
Any help would be appreciated.
Indexof() method will look for similarity in object references not the id itself. findIndex() method can help you here instead.
vm.hasFeature = function(item){
var hasElements= vm.data1.features.findIndex(function(e){
return e.id == item.id;
});
console.log(item, hasElements);
return hasElements;
}
And in html
<li ng-repeat="item in vm.data2.features"
ng-class="vm.hasFeature(item) > -1 ? 'check' : 'uncheck'">
vm.hasFeature = function(item){
var hasElements= vm.data1.features.findIndex(function(e){
return e.id == item.id;
});
console.log(item, hasElements);
return hasElements;
}
CodePen Link: https://codepen.io/anon/pen/ewgLBN?editors=1010
None of the objects will be the same because indexOf(item) will compare object references of item. You'll need to do a deep equals comparison of the items.
i.e.
{id: "TEST_TEXT2", desc: 'smth12'} === {id: "TEST_TEXT2", desc: 'smth12'} // false
vm.data1.features[0] === vm.data1.features[1] // false
Example using lodash would be something like:
_.some(vm.data1.features, otherItem => _.isEqual(item, otherItem))
Because
_.isEqual(vm.data1.features[0], vm.data2.features[1]) // true
Docs for Lodash:
_.some
_.isEqual

Update only selected array item in *ngFor array Angular2

I have an *ngFor loop to display libraries. When you click on a library, categories of that library appear beneath in a tree-like structure. The categories are also being displayed with an *ngFor loop. When I have one library expanded and click on another one, the categories in BOTH libraries are updating to the categories in the library that was just clicked. The functionality I am looking for is to only update the selected library categories and leave the others alone. There is a post here that seems to be close to my problem but I couldn't get it to work.
Using *ngFor to loop through an array and *ngIf to select which elements from the array to list
Here is my code:
library.component.html
<div *ngFor="let messageLibrary of onHoldMessageLibraryService.data" class="library mgn-top10 ft-size14">
<a (click)="onHoldMessageLibraryService.getSelectedMessageLibrary(messageLibrary)"><i class="fa fa-folder-o mgn-right10" aria-hidden="true"></i>{{messageLibrary.Name}}</a>
<library-category *ngIf="messageLibrary.treeIsExpanded" (displayMessagesFromSelectedCategory)="getOnHoldMessages()"></library-category>
</div>
library.service
public treeIsExpanded: boolean;
public selectMessages: boolean;
public data: TreeData[];
public selectedName: string;
public selectedValue: number;
public getSelectedMessageLibrary(messageLibrary): void {
this.selectedMessagesLibrary = messageLibrary;
this.selectedMessagesLibrary.treeIsExpanded = !this.selectedMessagesLibrary.treeIsExpanded;
}
public dummyData(): void {
let myTree: TreeData[] = new Array();
myTree.push(
{
Name: 'Banking Library',
Id: 1,
Category: [{
Name: 'Credit Cards',
Id: 11,
Category: null
}]
},
{
Name: 'Automobile Library',
Id: 2,
Category: [{
Name: 'Cars',
Id: 12,
Category: null
}]
},
{
Name: 'Coffee Library',
Id: 3,
Category: [{
Name: 'Americano',
Id: 13,
Category: null
}]
}
)
this.data = myTree;
}
library-category.component.html
<ul *ngFor="let category of onHoldMessageLibraryService.selectedMessagesLibrary.Category">
<li><i class="fa fa-folder-o mgn-right10" aria-hidden="true"></i>{{category.Name}}</li>
</ul>
tree-data.ts
export class TreeData {
Name: string;
Id: number;
Category: TreeData[];
}
public getSelectedMessageLibrary(messageLibrary): void {
this.selectedMessagesLibrary = messageLibrary;
this.selectedMessagesLibrary.treeIsExpanded = !this.selectedMessagesLibrary.treeIsExpanded; //this line is not giving desired result
}
to be replaced with
public getSelectedMessageLibrary(messageLibrary): void {
messageLibrary.treeIsExpanded = !messageLibrary.treeIsExpanded;
}
And it will be good if you use trackBy with NgFor

AngularJS filter to ng-repeat, exclude elements, if they in second array

I need filter for ng-repeat, that explode elements in "general" array, if element exist in "suggest" array (by id field).
$scope.general= [{id: 21323, name: 'alex'}, {id: 8787, name: 'maria'}, {id: 8787, name: 'artem'}];
$scope.suggest = [{id: 21323, name: 'alex'}, {id: 8787, name: 'maria'}];
<div ng-repeat="elem in general">{{elem.name}}</div>
You should create your own custom filter and you'll probably want to use Array.prototype.filter.
You said you wanted to exclude by the property id. The following filler optionally allows specifying a property. If the property is not specified, then the objects are excluded by strict equality (the same method used by the ===, or triple-equals, operator) of the objects.
angular.module('myFilters', [])
.filter('exclude', function() {
return function(input, exclude, prop) {
if (!angular.isArray(input))
return input;
if (!angular.isArray(exclude))
exclude = [];
if (prop) {
exclude = exclude.map(function byProp(item) {
return item[prop];
});
}
return input.filter(function byExclude(item) {
return exclude.indexOf(prop ? item[prop] : item) === -1;
});
};
});
To use this filter in your html:
<div ng-repeat="elem in general | exclude:suggest:'id'">{{elem.name}}</div>
Here is an example jsfiddle:
https://jsfiddle.net/6ov1sjfb/
Note that in your question artem's id matches maria's thus both artem and maria were filtered. I changed artem's id in the plunker to be unique to show that the filter works.
Try this :
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.general = [{
id: 21323,
name: 'alex'
}, {
id: 8787,
name: 'maria'
}, {
id: 8787,
name: 'artem'
}];
$scope.suggest = [{
id: 21323,
name: 'alex'
}, {
id: 8787,
name: 'maria'
}];
$scope.filteredArray = function () {
return $scope.general.filter(function (letter) {
for (i = 0; i < $scope.suggest.length; i++) {
return $scope.suggest[i].id !== letter.id
}
});
};
}
and
<div ng-repeat="elem in filteredArray(letters)">{{elem.name}}</div>
check out the fiddle : http://jsfiddle.net/o2er6msv/
Note: please chk ur id, they are duplicated

AngularJS filter object by property on tree structure

I am posting this because I never found a precise answer for filtering nested objects (tree sturcture).
Let's say we have an JSON tree structure that looks like this:
$scope.tree = [{
id: 1,
parent_id: 0,
name: 'Root Item',
items: [
{
id: 2,
parent_id: 1,
name: '1st Child of 1'
},
{
id: 3,
parent_id: 1,
name: '2nd Child of 1'
},
{
id: 4,
parent_id: 1,
name: '3rd Child of 1',
items:[
{
id:5,
parent_id: 4,
name:'1st Child of 5'
},
{
id:6,
parent_id: 4,
name:'2nd Child of 5'
}
]}
]
}]
How do we traverse the tree with a filter to get object with id 6 for example?
If we use the following filter for example:
<div data-ng-init="selectedItem = (tree | filter:{id:6})">
<h1>The name of item with id:6 is selectedItem.name</h1>
</div>
It will only iterate through the first level in which will only find id:1.
So, in order to get nested level objects we must use a recursive filter like this one:
angular.module("myApp",[])
.filter("filterTree",function(){
return function(items,id){
var filtered = [];
var recursiveFilter = function(items,id){
angular.forEach(items,function(item){
if(item.id === id){
filtered.push(item);
}
if(angular.isArray(item.items) && item.items.length > 0){
recursiveFilter(item.items,id);
}
});
};
recursiveFilter(items,id);
return filtered;
};
});
});
So, to use this filter in the markup you would call it like this:
<div data-ng-init="selectedItem = (tree | filterTree:6)">
<h1>The name of item with id:6 is selectedItem.name</h1>
</div>
Hope you find this useful, it took me some time to digest recursive filters.
Of course, this filter works to get 1 item since it returns [0] first object of filtered array. But if you want it to return more than 1 result you'll have to remove only that [0] at the return function and then use ng-repeat to iterate over filtered resutls.

How can I make a <select> dropdown in AngularJS default to a value in localstorage?

I have a <select> that I am populating with a list of values. Everything works and I see the expected list. But now I would like to have the value set to a locally stored value if available.
I have the code below. When I run this code the number after the Type in the label changes to match that in the localstorage but the select box does not change.
getContentTypeSelect: function ($scope) {
entityService.getEntities('ContentType')
.then(function (result) {
$scope.option.contentTypes = result;
$scope.option.contentTypesPlus = [{ id: 0, name: '*' }].concat(result);
$scope.option.selectedContentType
= localStorageService.get('selectedContentType');
}, function (result) {
alert("Error: No data returned");
});
},
<span class="label">Type {{ option.selectedContentType }}</span>
<select
data-ng-disabled="!option.selectedSubject"
data-ng-model="option.selectedContentType"
data-ng-options="item.id as item.name for item in option.contentTypesPlus">
<option style="display: none" value="">Select Content Type</option>
</select>
Here is the code I have that sets the value in localstorage:
$scope.$watch('option.selectedContentType', function () {
if ($scope.option.selectedContentType != null) {
localStorageService.add('selectedContentType',
$scope.option.selectedContentType);
$scope.grid.data = null;
$scope.grid.selected = false;
}
});
Here is the data stored in contentTypesPlus:
0: Object
id: 0
name: "*"
__proto__: Object
1: b
id: 1
name: "Page"
__proto__: b
2: b
id: 2
name: "Menu"
__proto__: b
3: b
id: 3
name: "Content Block"
__proto__: b
How can I make the select box go to the localstorage value if there is one?
Update
Still hoping for an answer and I would be happy to accept another if some person can help me. The answer given is not complete and I am still waiting for more information. I am hoping someone can give me an example that fits with my code. Thanks
I think you need to make sure selectedContentType needs to be the id field of the option object according to the comprehension expression defined in the select directive.
The comprehensive expression is defined as
item.id as item.name for item in option.contentTypesPlus
item.id will be the actual value stored in the ng-model option.selectedContentType
Say this is your select with ng-model
<select
ng-model="language.selected"
ng-init="language.selected = settings.language[settings.language.selected.id]"
ng-options="l.label for l in settings.language"
ng-change="updateLanguage(language.selected)">
In your controller
$scope.settings = {
enableEventsNotification: true,
enableiBeaconNotification: true,
hours: [
{ id: 0, label: '3h', value: 3 },
{ id: 1, label: '6h', value: 6 },
{ id: 2, label: '9h', value: 9 },
{ id: 3, label: '12h', value: 12 },
{ id: 4, label: '24h', value: 24 }
],
language: [
{ id: 0, label: 'English', value: 'en-us' },
{ id: 1, label: 'Francais', value: 'fr-fr' }
]
};
$scope.hours = [];
$scope.hours.selected = $localstorage.getObject('hours', $scope.settings.hours[1]);
$scope.settings.hours.selected = $localstorage.getObject('hours', $scope.settings.hours[1]);
$scope.hours.selected = $scope.hours.selected;

Resources