Two-tier sorting for ng-repeat - angularjs

For example, I have the following collection used for ng-repeat:
$scope.pokedex = [{
type: "Fire",
pokemon: ["Charizard", "Moltres"]
},{
type: "Rock",
pokemon: []
},{
type: "Fighting",
pokemon: ["Machamp", "Hitmonchan"]
},{
type: "Dragon",
pokemon: []
}];
This collection will be churned out in a ng-repeat directive. In the actual application, the collection will be retrieved from a database, so it may be unsorted. I want to sort it in the following manner: priority sort types with Pokemon to the top, then sort each group by name.
Edit: I need to clarify what the backend data consists of. In the above example, $scope.pokedex consists of a constant number of types - these are categories. The application retrieves Pokemon from the database and fill up each category's list accordingly. The full range of types is intentionally hardcoded into the array and will remain unchanged regardless of whether the list of Pokemon in it is empty or not.
When the web page is generated using ng-repeat, the desired end state is as follows:
An accordion is displayed with each type as a header, and the list of Pokemon in the body as a list/table.
All empty categories shall be disabled but still visible, their headers given a particular CSS format, and all of them PUSHED TO THE BOTTOM beneath the non-empty categories.
The empty group and non-empty group shall individually be sorted by category/type name.
Everything except the pushing of empty groups to the bottom and the sorting by name have been implemented. These are my final requirements to implement.
How can I do that? Is there a way to do it in advance, or via orderBy during ng-repeat, or any other workable solution?

You Can Try like this This
<ul>
<li ng-repeat="people in peoples | orderObjectBy:'pokemon':true">{{ people.name }}</li>
</ul>

Please try the code below
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="pokemonController">
<ul>
<li ng-repeat="people in peoples | orderBy:['-pokemon.length','name']">
name: {{people.name}} - number of Pokemons: {{people.pokemon.length}}
<hr />
</li>
</ul>
</div>
</div>
<script>
var app = angular.module('myApp',[]);
app.controller('pokemonController', ['$scope', function($scope){
$scope.peoples = [{
name: "Obama",
pokemon: ["Charizard", "Dragonite", "Moltres"]
},{
name: "Merkel",
pokemon: []
},{
name: "Putin",
pokemon: ["Machamp", "Hitmonchan"]
},{
name: "Kim",
pokemon: []
}];
}]);
</script>

Related

how do i filter items by property in ng-options?

I have some data and a select control as you can see from my code sample below. It works for me but I want to be able to filter the data by type. In my code sample, I show one way I have tried to filter the data based on the type being a value of one. I am not sure if the way I am creating the select is causing this simple inline filter to not work or if I am doing something wrong?
$scope.data = [{
name : "5 play",
type : 1
}, {
name : "one on one",
type : 2}, {
name : "two on one",
type : 2}];
<select class="form-control" ng-model="selectedRule"
ng-options="item as (item.Name) for item in data track by item.Name | filter:{type:1}"></select>
As stated in angular docs, track by must always be the last expression:
Note: track by must always be the last expression
My guess is that all that follows is not considered, so that´s why your filter wasn´t applying. Moving the track by to the right place should fix it
Yes, as mentioned. Move the track by at the end and make the letter N lowercase like in the following demo or in this fiddle.
angular.module('demoApp', [])
.controller('mainController', MainController);
function MainController($scope) {
$scope.data = [{
name: "5 play",
type: 1
}, {
name: "one on one",
type: 2
}, {
name: "two on one",
type: 2
}];
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="mainController">
<select class="form-control" ng-model="selectedRule" ng-options="item as item.name for item in data | filter:{type:1} track by item.name"></select>
</div>

How do perform an "except" filter in Angular?

Suppose that I have an Angular view that allows a user to check books out of a library. My data model consists of two arrays of Book entities which each have a unique ID field plus a title field. The first array contains an entity for every book in the library and the second array contains an entity for every book that the user has checked out.
libraryBooks = [{
id: 0,
title: "The Adventure of Tom Sawyer"}, {
id: 1,
title: "Moby Dick" }, {
id: 2,
title: "To Kill a Mockingbird" }, {
id: 3,
title: "The Three Little Pigs" }];
checkedOutBooks = [{
id: 0,
title: "The Adventure of Tom Sawyer"}, {
id: 3,
title: "The Three Little Pigs" }];
In short, the library has four books and the user has checked out two. If I want to list the books from both arrays, I can write this:
<h1>Library Books</h1>
<div ng-repeat="book in libraryBooks">
{{ book.title }}
</div>
<h1>Checked out Books</h1>
<div ng-repeat="book in checkedOutBooks">
{{ book.title }}
</div>
Suppose I want to display a third list: the subset of library books that the user has not checked out.
I have seen examples where the Angular "filter" is used to specify one particular value that should not be matched in order to narrow down a list, but in this case, I want to exclude multiple values, so how do I go about doing this?
I have seen examples where a custom filter is added to an Angular module, but I think that in this case, any custom filter should be scoped to this controller.
I've got this figured out. The solution is to write a filter function and attach it to $scope like so:
function filter_notCheckedOut(book) {
var i;
for (i = 0; i < that.libraryBooks.length; i += 1) {
if (that.libraryBooks[i].id === page.id) {
return false;
}
}
return true;
}
In the view, it can then be referenced like this:
<h1>Books not checked out</h1>
<div ng-repeat="book in libraryBooks | filter:filter_notCheckedOut">
{{ book.title }}
</div>

Ng-repeat inside ng-repeat

<ul ng-repeat="cate in restaurant.categories"><li>{{cate}}</li>
<li ng-repeat="menuItem in restaurant.menuItems" ng-show="menuItem.category == cate">{{menuItem.name}}</li></ul>
I want one ng-repeat loop inside another and to show the menu only if the menuItem is in the category. I only have items in the first category loop, and empty for all the other categories.
Categories and menuItem are 2 different arrays. If the menuItem's category is under the current category it should be added to the page.
menuItems = {{name: dish1, category:soup},
{name: dish2, category:beef}}
categories = {beef, soup}
#show-me-the-code : Bill Bi has two different array. So the best option to achieve this is by filter in inside loop as stated in my comment.
Here is the final code with filter for inside loop. I am including fiddler for quick reference.
<div ng-app ng-controller="testCtrl">
<ul ng-repeat="cate in categories">
<li>{{cate}}</li>
<li ng-repeat="menuItem in menuItems | filter:{category: cate}">{{menuItem.name}}</li>
</ul>
</div>
function testCtrl($scope) {
$scope.menuItems = [{name: 'dish1', category:'soup'},
{name: 'dish2', category:'beef'}];
$scope.categories = ['beef', 'soup']
}
Fiddle : JSFiddle
I would change my data representation to match what you are actually trying to display like so:
$scope.restaurant = {
categories: [{
name: "beef",
menuItems: [{
name: "dish1",
"price": "$10"
}, {
name: "dish2",
"price": "$15"
}]
}, {
name: "soup",
menuItems: [{
name: "dish1",
"price": "$20"
}, {
name: "dish2",
"price": "$25"
}]
}]
};
This way you could easily match your two nested loops like this:
<div ng-app ng-controller="testCtrl">
<ul ng-repeat="cate in restaurant.categories">
<li>{{cate.name}}</li>
<li ng-repeat="menuItem in cate.menuItems">{{menuItem.name}} - {{menuItem.price}}</li>
</ul>
</div>
Check out this fiddle if you would like to see it in action.
If you need to stick to your JSON data, you will have to do filtering to pull the contents you want to display.

angularJS - How to filter by one item in object only

In this JSFiddle (https://jsfiddle.net/eug0taf9/9/), I select a rating and expect an image to be displayed.
What is happening?
When I select rating 'High', 2 images show up instead of 1 because the filter is catching category 'High' and also description 'High' of an image in category 'Low'.
What do I expect to happen?
On selecting rating "High", I only want the category to be filtered. I don't need it to be filtered by description too.
Any suggestions on how to solve this?
HTML code:
<div ng-app="myApp" ng-controller="myCtrl">
<span>Select a rating</span>
<select ng-model="catselect"
ng-options="category for category in imageCategories"
ng-change="valueSelected(catselect)">
<option value>All</option>
</select>
<!--<p>Select <input ng-model="categories"/></p>-->
<ul class="photosmenu">
<li ng-repeat="image in images | filter : catselect" ng-click="setCurrentImage(image)">
<img ng-src="{{image.image}}" alt="{{image.stars}}" width="300px"/>
</li>
</ul><!-- End of List -->
</div>
Angular Code:
var mod = angular.module("myApp", []);
mod.controller("myCtrl", function($scope){
$scope.images = [
{category: 'High', image: 'img/1.png', description: 'Random Photo', stars: '4/5'},
{category: 'Medium', image: 'img/6.png', description: 'ApplePhoto', stars: '3/5'},
{category: 'Low', image: 'img/13.png', description: 'High Top Photo', stars: '2/5'},
{category: 'None', image: 'img/16.png', description: 'Kilt Photo', stars: '0/5'}];
$scope.currentImage = $scope.images[0];
$scope.imageCategories = ["High", "Medium", "Low", "None"];
$scope.valueSelected = function (value) {
if (value === null) {
$scope.catselect = undefined;
}
};
});
That is because you have a global match filter which will match on all properties, if you want to filter on specific property set your filter object accordingly. i.e
<li ng-repeat="image in images | filter :{category: catselect}"
Demo
or you could also set your ng-model to an object,
<select ng-model="catselect.category"
and do:
<li ng-repeat="image in images | filter :catselect"
Demo
Check out documentation:
string: The string is used for matching against the contents of the array. All strings or objects with string properties in array that match this string will be returned. This also applies to nested object properties. The predicate can be negated by prefixing the string with !.
Object: A pattern object can be used to filter specific properties on objects contained by array. For example {name:"M", phone:"1"} predicate will return an array of items which have property name containing "M" and property phone containing "1". A special property name $ can be used (as in {$:"text"}) to accept a match against any property of the object or its nested object properties. That's equivalent to the simple substring match with a string as described above. The predicate can be negated by prefixing the string with !. For example {name: "!M"} predicate will return an array of items which have property name not containing "M".

AngularJS filter on empty string

I have a simple Angular filter setup between a select and a list. When a user selects an item from the dropdown, the list is updated to show the matching result. The problem is that I have a "Select..." option first in the dropdown with no value, and when that is the selected value, all items are shown. I suppose that makes sense, but I want the opposite. If the selected option has no value, I don't want to show the list. Here are some relevant bits, followed by a link to a full fiddle:
The dropdown:
<select class="world-list" ng-model="selectedWorld" ng-options="world.worldId as world.worldName for world in allWorlds">
<option value="">Select...</option>
</select>
The list:
<ul class="unstyled" id="charList">
<li ng-repeat="char in characters | filter:selectedWorld">
{{char.charName}} - {{char.charRace}} {{char.charClass}}
</li>
</ul>
And here is a link to the full fiddle, which contains my JSON structures that drives all this: http://embed.plnkr.co/6XUmC5efO0Y1BRNLRUig
What I'm trying to do is rather simple, and I'm pretty new to Angular so I'm sure I'm missing something obvious. Checked the Jabbr room, nobody on. :(
Anyway, thanks for any and all help!
One way to accomplish this is to filter by specific properties of the iteration object:
<ul class="unstyled" id="charList">
<li ng-repeat="char in characters | filter:{worldId: selectedWorld}">
{{char.charName}} - {{char.charRace}} {{char.charClass}}
</li>
</ul>
Plunker
I noticed in your code "script.js" you don't seem to be accounting for the blank option.
I did not test this (I probably should prior to posing this answer), but it should work by placing the "blank" option within your script.js.
app.controller('WorldCtrl', function ($scope, $http) {
$scope.allWorlds = [{
worldId: "",
worldName: ""
},
{
worldId: "b8ee0530-744b-463e-9428-23178f6c7bff",
worldName: "World 1"
},
{
worldId: "81211982-5613-4f9c-b704-7b6fa35faf84",
worldName: "World 2"
},
{
worldId: "df41208e-e8d2-46c9-8299-8f37632a51f8",
worldName: "World 3"
}];
$scope.characters = [{
charClass: "Rogue",
charName: "Vaes",
charRace: "Human",
worldId: "b8ee0530-744b-463e-9428-23178f6c7bff"
},
{
charClass: "Warrior",
charName: "Azash",
charRace: "Orc",
worldId: "b8ee0530-744b-463e-9428-23178f6c7bff"
},
{
charClass: "Mage",
charName: "Anele",
charRace: "Ogre",
worldId: "81211982-5613-4f9c-b704-7b6fa35faf84"
}];
});

Resources