AngularJS ng-repeat issue with removing duplicate keys in an object - javascript

I'm trying to use ng-repeat to repeat the rest of data if the attribute is the same. This may not sound clear so here is an example.
JSON sample, which is an array of books
[{"Genre":Sci-fic,"Name":"Bookname1"},
{"Genre":Sci-fic,"Name":"Bookname2"},
{"Genre":Non sci-fic,"Name":"Bookname3"},
{"Genre":Non sci-fic,"Name":"Bookname4"}]
So if the book belongs in the same genre, then we would have genre type as the heading of the accordion and list the rest of the attributes for the ones whose genre is that without repeating the heading. The problem I've been encountered is that since json array of objects comes from a data model, and I can't really filter the type without repeating the genre type.
here is what I'm having where I'm trying to have heading as the Book.Genre and inside that accordion-group will have the list of all the books whose genre is the heading.
http://plnkr.co/edit/PwIrOAIT3RRaE2ijzGZb?p=preview

I haven't been able to tackle this problem yet either... The best solution I can think of is to simply strip out the duplicates before adding that model to your scope...
Using your jsFiddle example...
http://jsfiddle.net/mcpDESIGNS/Uz5tM/1/
var books = [
{"Genre":"Sci-fic", "Name":"Bookname1"},
{"Genre":"Sci-fic", "Name":"Bookname2"},
{"Genre":"Non sci-fic", "Name":"Bookname3"},
{"Genre":"Non sci-fic", "Name":"Bookname4"}
];
var unique = [],
blocked = [];
unique.push(books[0]);
blocked.push(books[0].Genre);
for (var i = 1; i < books.length; i++) {
if (blocked.indexOf(books[i].Genre) <= -1) {
unique.push(books[i]);
blocked.push(books[i].Genre);
}
}
$scope.Books = books;
// [{Genre: "Sci-fic", Name: "Bookname1"},
// { Genre: "Non sci-fic", Name: "Bookname3" }];

it happens because you 've got duplicate value in generes use track by
<div ng-repeat="genere in generes track by $index"></div>
or if you want group you model by Genre you can use underscore.js please see that fiddle
View :
<div ng-app="app">
<div ng-controller="ParentCtrl">
<ul class="about-tab-titles" close-others="oneAtATime">
<li ng-repeat="(key ,book) in model">
{{key}}
<ul>
<li ng-repeat="b in book ">{{b.Name}}</li>
</ul>
</li>
</ul>
</div>
</div>
JS:
angular.module('app', [])
function ParentCtrl($scope) {
var books = [{
"Genre": "Sci-fic",
"Name": "Bookname1"
}, {
"Genre": "Sci-fic",
"Name": "Bookname2"
}, {
"Genre": "Non sci-fic",
"Name": "Bookname3"
}, {
"Genre": "Non sci-fic",
"Name": "Bookname4"
}];
$scope.model = _.groupBy(books, 'Genre');
}

So I found a solution which works for me. I followed this AngularJs filter on nested ng-repeat. I made a couple changes for my data model, but it's the same idea.

Related

Can I make an attribute appear only once in a vue v-for

I have an array of people with associated teams. I want to display all the people in the record, but I only want to display their team name once.
Meaning, if the v-for loop has encountered this particular team name, it should put it in a new temporary array to signify that it should be unique, then when it encounters that team name again, checks it through that temporary array and prevent it from showing again.
Sample HTML Code:
<div id="a-list">
<div v-for="person in people">{{person.Name}}, {{person.Team}}</div>
</div>
Sample Vue Code:
var crew = new Vue({
el: "#a-list",
data: {
people:
[ { "Name": "Richard","Team":"DMS"}, { "Name": "Mark","Team":"VV"}, { "Name": "Steve","Team":"VV"}, {"Name":"Koji","Team":"MZ"}, {"Name":"Jamie","Team":"VV"} ]
}
});
Expected Output:
Richard, DMS
Mark, VV
Steve,
Koji, MZ
Jaimie,
Is this possible to do directly from the v-for loop and not in the JS file?
Edited to show more data that are not sequential
Update: As Fabio has pointed out, the above scenario wouldn't make much sense unless the order of the team is arranged sequentially in the output first. So his answer is correct.
This could be a solution:
<div id="a-list">
<div v-for="(person,index) in people"> {{person.Name}}, {{ ((index == 0) || person.Team != people[index-1].Team) ? person.Team : '' }}</div>
</div>

Push object from one array to another based on parameter

So I have a list of news articles, and I need them to be filtered but server side not client side.
The way I'm filtering is by having a dropdown list that users click on to filter the articles like so:
<ul>
<li ng-repeat="category in categories">
<span href="" ng-click="getFilteredArticles(category.id)">{{category.title}}</span>
</li>
</ul>
and the articles are populated via an ng-repeat like so
<ul infinite-scroll="addPosts()" infinite-scroll-distance="0" infinite-scroll-disabled="stopScrolling" class="c-news">
<li ng-repeat="post in posts" class="c-news__item" ng-click="selectPost(post)">
<!-- Omitted some code for the sake of brevity -->
</li>
</ul>
The getFilteredArticles method looks like this
$scope.getFilteredArticles = function (categoryId) {
var posts = $scope.posts;
posts.forEach( (object) => {
if ( object[Categories.Id] === categoryId ) {
$scope.filteredArticles.push(object);
}
});
console.log($scope.filteredArticles);
}
A typical JSON object that I'm pulling through looks like this
{
"Title":"Test Title",
"Summary":"",
"PublishedDate":"2016-10-17T09:42:00",
"Author":{
"Id":"480586a5-2169-e611-9426-00155d502902",
"FirstName":"TestFirst",
"LastName":"TestSecond",
"Email":"test#test.com"
},
"Id":99,
"StatusName":"Published",
"Status":2,
"Categories":[
{
"Id":1,
"Name":"Category 1",
"ArticleCount":31,
"UnpublishedArticleCount":1
},
{
"Id":2,
"Name":"Category 2",
"ArticleCount":19,
"UnpublishedArticleCount":0
}
],
"AttachmentCount":0,
"FilesAwaitingCheckIn":0
}
What I'd like to happen is when the user clicks one of the filter choices, for the list to then filter to the clicked choice. I've got this far but then I'm getting a ReferenceError: Categories is not defined.
I based my getFilteredArticles() code from another Stack question which can be found here.
I'm aware of being able to just filter the ng-repeat however my manager does not want to go that route and would rather filter server side due to the amount of posts that we may have.
Any ideas?
EDIT
Here's what's inside $scope.posts array
As Categories is an array you need to loop through that array to get Id's
Your loop should be similar to this
$scope.getFilteredArticles = function (categoryId) {
for(var i=0;i<$scope.posts.length;i++)
{
for(var j=0;j<$scope.posts[i]["Categories"].length;j++)
{
if($scope.posts[i]["Categories"][j]["Id"] == categoryId)
$scope.filteredArticles.push($scope.posts[i])
}
}
console.log(JSON.stringify($scope.filteredArticles));
};

Angular ng-options selected object

why is this not working?
HTML:
<div ng-controller="MyCtrl">
<select class="rp-admin-rooms-selected-select" name="teacherSelect" id="teacher"
ng-model="teacherSel"
ng-options="teacher.name for teacher in teachers "
>
</select>
{{teacherSel}}
</div>
JS:
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.teachers = [
{name:'Facundo', id:1},
{name:'Jorge', id:3},
{name:'Humberto', id:5},
{name:'David', id:7},
]
$scope.teacherSel = {name:'Facundo', id:1};
}
I would expect to be the selected element be Facundo
The thing is, I know that its possible to do this via teacherSel = id
and ng-options="teacher.name as teacher.id"...
But I have the object yet their, and I need the new object. not just the id.
jsfiddle:
http://jsfiddle.net/Lngv0r9k/
Michael Rose got it right. Another option would be force angular to do the comparison by value using a track by statement, as follows:
ng-options="teacher.name for teacher in teachers track by teacher.ID"
This works in angular 1.2 and after.
Updated fiddle: http://jsfiddle.net/Lngv0r9k/3/.
The issue is that the comparison for the selected entry is done via reference, not value. Since your $scope.teacherSel is a different object than the one inside the array - it will never be the selected one.
Therefore you need to find the selected entry inside the array and then use this as follows: $scope.teacherSel = $scope.teachers[indexOfSelectedEntry].
See at the bottom of your updated jsfiddle: http://jsfiddle.net/Lngv0r9k/1/
On your example you dont give a teacher object to the room.teacher , so the ng-options cant match anything to the ng-model.
As you see on the screen below, value=? means that it cant find correct value to match up.
you could try for example:
$scope.room = {
teacher: $scope.teachers[an item index];
};
OR
$scope.room = {
teacher: {
"ID": "1",
"name": "Adolf Ingobert",
"short": "AD",
"display": "Adolf I.",
"sectionFK": "2",
"invisible": "0"
};
};

ngRepeat Filter by Array name?

I have the following JSON structure in my Angular app:
Sample: http://pastie.org/pastes/9476207/text?key=u6mobe15chwyiz1jakn0w
And the following HTML which is displaying every product in the parent array:
<div class="bb-product-grid" ng-repeat="brand in notebooks">
<ul ng-repeat="products in brand">
<li ng-repeat="Notebook in products">
<span class="product-title">{{Notebook.Model}}</span>
<span class="product-description"><p>{{Notebook.Description}}</p></span>
<span class="product-image">image</span>
<span class="product-add-to-bundle"><button>Add to bundle</button></span>
<span class="product-price">{{Notebook.Price}}<sub>pcm</sub></span>
</li>
</ul>
</div>
I want to be able to filter the products by the brand names (array names, Acer, Apple etc.).
By default, only the 'Apple' products will be visible. You will then click a button to change the visible results to reflect what brand you've selected. So if you then select HP, you'll only see 'HP' products.
I'm a little stuck on this because the JSON structure is pretty nested, if it was 1/2 levels I'd be able to cope but I can't figure this out, I seem to only show all the products or break the app. Any advice on the best-practise approach to this (I'm guessing I might have to create a custom filter?) will be greatly appreciated!
If you can get your data to be in this format:
$scope.data = [
{
brand: 'Acer',
laptops: ['acer_1', 'acer_2', 'acer_3', 'acer_4', 'acer_5']
},
{
brand: 'Apple',
laptops: ['apple_1', 'apple_2', 'apple_3', 'apple_4', 'apple_5']
},
{
brand: 'Asus',
laptops: ['asus_1', 'asus_2', 'asus_3', 'asus_4', 'asus_5']
},
{
brand: 'HP',
laptops: ['hp_1', 'hp_2', 'hp_3', 'hp_4', 'hp_5']
},
{
brand: 'Lenovo',
laptops: ['lenovo_1', 'lenovo_2', 'lenovo_3', 'lenovo_4', 'lenovo_5']
},
{
brand: 'Toshiba',
laptops: ['toshiba_1', 'toshiba_2', 'toshiba_3', 'toshiba_4', 'toshiba_5']
}
];
Then you can use have a filter like so:
$scope.search = {
brand: 'HP'
};
With HTML:
<select ng-model="search.brand">
<option ng-repeat="company in data">{{company.brand}}</option>
</select>
<ul ng-repeat="company in data | filter:search">
<li><b ng-bind="company.brand"></b></li>
<ul ng-repeat="laptop in company.laptops">
<li ng-bind="laptop"></li>
</ul>
</ul>
Here is a jsfiddle.
brands is an array of objects with the brand names as keys. It doesn't make sense to use filters to solve this problem ... however it's easy to get the products if you have the key name. Imagine you have searchName = 'Apple' and obj = [{Apple: []}, {Acer: []}]. You could just use obj[0].Apple.
this.filterItem = "Apple";
this.productsToShow = productObject[0][this.filterItem];
<select ng-model=ctrl.filterItem><!-- brand names go here --></select>
<ul ng-repeat="product in ctrl.productsToShow">
Don't use filter, when only ever one is supposed to be visisble. Instead, skip the iteration over the brands, and instead just iterate over the selected brand. All you need to do is then make sure you set selectedBrand to the correct subset of your data structure.
Fiddle: http://jsfiddle.net/5wwboysu/2/
Show brand:
<span ng-repeat="(name,brand) in notebooks.Notebooks[0]" ng-click="selected(brand)">
{{name}} - <br>
</span>
<ul ng-repeat="products in selectedBrand">
<!-- snip -->
</ul>
$scope.selected = function(brand) {
$scope.selectedBrand = brand;
};
However, if you have control over the JSON, I'd try to return it in a format better suited for what you're trying to do.
Add this filter which will take a brand:
<ul ng-repeat="brand in notebooks| filter:filterBrand">
in controller
$scope.SelectedBrandName= "Apple";
$scope.filterBrand = function(brand){
if(brand.Name == $scope.SelectedBrandName)
return true;
return false;
};
This will look at each brand and only allow brands that return true. If you have a button that changes the $scope.SelectedBrandName value to a different name, it will automatically filter through the ng-repeat to only brands that match.

On click How can I cycle through JSON one by one in AngularJS

Creating my first directive as an exercise in angular —making more or less a custom carousel to learn how directives work.
I've set up a Factory with some JSON data:
directiveApp.factory("Actors", function(){
var Actors = {};
Actors.detail = {
"1": {
"name": "Russel Brand",
"profession": "Actor",
"yoga": [
"Bikram",
"Hatha",
"Vinyasa"
]
},
"2": {
"name": "Aaron Bielefeldt",
"profession": "Ambassador",
"yoga": [
"Bikram",
"Hatha",
"Vinyasa"
]
},
"3": {
"name": "Adrienne Hengels",
"profession": "Ambassador",
"yoga": [
"Bikram",
"Hatha",
"Vinyasa"
]
}
};
return Actors;
});
And an actors controller:
function actorsCtrl($scope, Actors) {
$scope.actors = Actors;
}
And am using ng-repeat to display the model data:
<div ng-controller="actorsCtrl">
<div ng-repeat="actor in actors.detail">
<p>{{actor.name}} </p>
</div>
<div ng-click="next-actor">Next Actor</div>
</div>
1) How do I only display the actor's name in the first index of my angular model actors.detail?
2) How do I properly create a click event that will fetch the following index and replace the previous actor.name
User flow:
Russell Brand is Visible
click of next-actor ->Russell Brand's name is replaced with Aaron Bielefeldt
Since you only want the current user, the ng-repeat is not what you want to use, since that would be for each element in the data;
You would want to keep track of the index you are looking at in the scope, and increment that.
<div ng-controller="TestController">
{{data[current].name}}
<div ng-click="Next();"> NEXT! </div>
</div>
Where in the controller we also have these set up, where data is your actors:
$scope.current = 0;
$scope.Next = function() {
$scope.current = ($scope.current + 1) % $scope.data.length;
};
Here's a fiddle where it's done.
I would change my serivce to return a single actor and maintain the index in the controller.
something like this. This is an incomplete solution - you need to take care of cycle etc...

Categories