How to separate groups in ng-repeat - angularjs

I have items I want to show, normally by using ng-repeat. I want to show the in some order (easy), but whenever the ordered attribute changes, I want some HTML in-between.
Example: (Fiddle):
<div ng-app ng-controller="Main">
<div ng-repeat="item in items | orderBy:'role'">
{{item.role}} - {{item.name}}
</div>
</div>
function Main($scope){
$scope.items = [{name: 'First', role: 1},
{name: 'Second', role:2},
{name: 'Third', role: 1},
{name: 'Fourth', role: 2}];
}
I want it to print:
1 - First
1 - Third
(some separator kode)
2 - Second
2 - Fourth

You will want to create a function in your scope.
$scope.currentRole = 'something';
$scope.CreateHeader = function(role) {
showHeader = (role!=$scope.currentRole);
$scope.currentRole = role;
return showHeader;
}
And then in your HTML:
<div ng-app ng-controller="Main">
<div ng-repeat="item in items | orderBy:'role'">
<div ng-show="CreateHeader(item.role)">
something here for the header
</div>
{{item.role}} - {{item.name}}
</div>
</div>

The solution by #lucuma only works on the first time through the loop. If Angular refreshes the list, the variable will still be set from the previous loop.
Instead, I added a new attribute to the list (say, header) during initialization:
function Main($scope){
$scope.items = [{name: 'First', role: 1, header: false},
{name: 'Second', role:2, header: false},
{name: 'Third', role: 1, header: true},
{name: 'Fourth', role: 2, header: false}];
}
Then the HTML by #lucuma works:
<div ng-app ng-controller="Main">
<div ng-repeat="item in items | orderBy:'role'">
<div ng-show="item.header"> // <== with this change
something here for the header
</div>
{{item.role}} - {{item.name}}
</div>
</div>
Oh, and you could sort the list once at initialization and remove the orderBy filter.

Related

Angular using ng-repeat variable in nested ng-repeat

I have an ng-repeat which iterates over an array of values:
var englishList = ["abo", "ser", "vol", "con", "giv", "blo"];
$scope.englishList = englishList;
Is there a way to loop over these values in an ng-repeat and use the returned value as part of a nested ng-repeat?
<div ng-repeat="api in englishList">
<div ng-repeat="result in searchData.abo | filter:searchText">
<li>{{result.title}} {{result.shortname}}</li>
</div>
</div>
Ideally, I'd like this line to interpolate the each ng-repeat value from $scope.englishList:
<div ng-repeat="result in searchData.{{api}} | filter:searchText">
Is there a way to do this in angular?
You should be able to do something like this, surely:
<div ng-repeat='api in englishList'>
<div ng-repeat='item in searchData[api]'>
<!-- uses the value of 'api' as a key in 'searchData' -->
<!-- you do not need to use interpolation here as these are already expressions -->
</div>
</div>
I can't really give a complete example as your code is not exactly obvious in how you would want to use the nested type, but the above snippet should give you an idea of HOW to use nested repeats.
I would advise you use an object model like so
{ "api": {
"foo": [ "bar", "baz", "qux" ]
}}
Rather than having two different arrays. This should make it less brittle. Remember that your view's logic should ideally be as simple as possible and it shouldn't have to do much manipulation on the data given to it to work. I would say that iterating one array and then iterating another using the values of array 1 as keys of array 2 is maybe a bit too much for the view to do.
Just use the bracket notation to dynamically access a property :
<div ng-repeat="api in englishList">
<div ng-repeat="result in searchData[api] | filter: searchText" >
<li>{{result.title}}, {{result.shortname}}</li>
</div>
</div>
Snippet :
angular.module('demoApp', []).controller('DemoController', function ($scope) {
$scope.englishList = ["abo", "ser", "vol", "con"];;
$scope.searchData = {
abo: [{
title: 'title abo',
shortname: 'shortname abo'
}],
ser: [{
title: 'title ser 1',
shortname: 'shortname ser 1'
}, {
title: 'title ser 2',
shortname: 'shortname ser 2'
}],
vol: [{
title: 'title vol',
shortname: 'shortname vol'
}],
con: [{
title: 'title con',
shortname: 'shortname con'
}]
};
});
p {
font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="demoApp" ng-controller="DemoController">
<div>Search <input type="text" ng-model="searchText"/></div>
<div ng-repeat="api in englishList">
<p>{{api}}</p>
<div ng-repeat="result in searchData[api] | filter: searchText" >
<li>{{result.title}}, {{result.shortname}}</li>
</div>
</div>
</div>

AngularJS filter nested ng-repeat based on repeated object properties

I have an array of restaurant objects and I want to list them by grouping their cities
My object is like;
restaurant = {
id: 'id',
name: 'name',
city: 'city'
}
This HTML Markup can give some info about what I want to do.
<div ng-repeat="restaurant in restaurant | filter: ???">
<div class="header">
<h1 ng-bind="restaurant.city"></h1>
<a>Select All</a>
</div>
<div class="clearfix" ng-repeat="???">
<input type="checkbox" id="restaurant.id" />
<label ng-bind="restaurant.name"></label>
</div>
</div>
Can I do it with one single array or do i need to create seperate city and restaurant arrays to do it?
If you want to group restaurants by city, you can use groupBy of angular.filter module.
Just add the JS file from here: http://www.cdnjs.com/libraries/angular-filter to your project and use following code.
var myApp = angular.module('myApp',['angular.filter']);
function MyCtrl($scope) {
$scope.restaurants = [
{id: 1, name: 'RestA', city: 'CityA'},
{id: 2, name: 'RestB', city: 'CityA'},
{id: 3, name: 'RestC', city: 'CityC'},
{id: 4, name: 'RestD', city: 'CityD'}
];
}
<div ng-controller="MyCtrl">
<ul ng-repeat="(key, value) in restaurants | groupBy: 'city'">
<b>{{ key }}</b>
<li ng-repeat="restaurant in value">
<i>restaurant: {{ restaurant.name }} </i>
</li>
</ul>
</div>
I've created JSFiddle for you with working example.

AngularJS: Count Filtered Items

I am showing subsets of a list if a checkbox is checked. I would like to replace the X next to the checkbox with the count of the list matching the selection criteria. I have a plunker that does everything but count the subset here.
My Controller looks like this:
var app = angular.module('app', []);
app.controller('MainController', function($scope){
$scope.cbMarvel = true;
$scope.cbDCComics = true;
$scope.heroes = [
{
id: 1,
name: 'Iron Man',
fname: 'Tony',
lname: 'Stark',
location: 'Stark Tower',
comic: 'Marvel'
},
{
id: 2,
name: 'Batman',
fname: 'Bruce',
lname: 'Wayne',
location: 'Bat Cave',
comic: 'DC'
},
{
id: 3,
name: 'Superman',
fname: 'Clark',
lname: 'Kent',
location: 'Metroplis',
comic: 'DC'
},
{
id: 1,
name: 'Daredevil',
fname: 'Jack',
lname: 'Murdock',
location: 'Court Room',
comic: 'Marvel'
},
{
id: 5,
name: 'Flash',
fname: 'Barry',
lname: 'Allen',
location: 'Speedline',
comic: 'DC'
},
{
id: 6,
name: 'Hulk',
fname: 'Bruce',
lname: 'Banner',
location: 'Labratory',
comic: 'Marvel'
},
{
id: 7,
name: 'Hawkeye',
fname: 'Clint',
lname: 'Barton',
location: 'Nest',
comic: 'Marvel'
},
{
id: 8,
name: 'Thor',
fname: 'Donald',
lname: 'Blake',
location: 'Asgard',
comic: 'Marvel'
}
];
});
And my view looks like this:
<!DOCTYPE html>
<html ng-app="app">
<head>
<link data-require="bootstrap#*" data-semver="3.2.0" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.css" />
<link data-require="bootstrap-css#*" data-semver="3.2.0" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" />
<script data-require="bootstrap#*" data-semver="3.2.0" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.js"></script>
<script data-require="jquery#2.0.3 current" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script data-require="angular.js#1.2.20" data-semver="1.2.20" src="https://code.angularjs.org/1.2.20/angular.js"></script>
<script data-require="angular-ui-bootstrap#*" data-semver="0.11.0" src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.11.0.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="MainController">
<fieldset>
<legend>Comments Log</legend>
<div class="row">
<div class="col-md-4">
<input type="checkbox" ng-model="cbMarvel"/> Marvel [X]
</div>
<div class="col-md-4"> </div>
<div class="col-md-4">
<input type="checkbox" ng-model="cbDCComics"/> DC Comics [X]
</div>
</div>
<div class="row"> </div>
<div class="row col-md-10">
<div ng-if="heroes.length == 0"><b>No Heroes Found!</b>
</div>
<div ng-repeat="h in heroes | filter:{comic:'Marvel'}" ng-show="cbMarvel">
{{ h.name}} - {{h.comic}}
</div>
<div ng-repeat="h in heroes | filter:{comic:'DC'}" ng-show="cbDCComics">
{{ h.name}} - {{h.comic}}
</div>
</div>
</fieldset>
</body>
</html>
Assuming your list of people is in data variable and you filter people using query model, the following code will work for you:
Number of visible people: {{(data|filter:query).length}}
Total number of people: {{data.length}}
summary
{{data.length}} - prints total number of people
{{(data|filter:query).length}} - prints filtered number of people
You could set that count in the view model itself while binding the data or just have a method on the scope that returns the count.
app.controller('MainController', function($scope, filterFilter){
....
$scope.getCount = function(strCat){
return filterFilter( $scope.heroes, {comic:strCat}).length;
}
...
});
and use it as:-
Marvel [{{getCount("Marvel")}}]
.....
DC Comics [{{getCount("DC")}}]
Plnkr
If the list is non changing when you are on the page i would suggest finding out the length and binding it to a property in the view model itself, and use it in the view.
//Set your data model
$scope.cbMarvel = {value:true, count:getCount('Marvel')};
$scope.cbDCComics = {value:true, count:getCount('DC')};
and in your view
<input type="checkbox" ng-model="cbMarvel.value"/> Marvel [{{cbMarvel.count}}]
Plnkr2
If your dataset is huge, instead of using filter inside the getCount, use a forEach and populate the count for each type at once.
Infact you do not need a filter at all, it seems inefficient to iterate through the same list using a filter in your case. Your's is a static list so categorize it in the controller itself.
var comics = $scope.comics = {}; //Dictionary of comics
//Create the collection here.
angular.forEach(heroes, function(itm){
if(!comics[itm.comic]){
comics[itm.comic] = {name:itm.comic, value:true, count:1, items:[itm] };
return;
}
comics[itm.comic].count++; //Incr count
comics[itm.comic].items.push(itm); //push specific item
});
and remove all the filters in your view and do:-
<div ng-repeat="h in comics.Marvel.items" ng-show="comics.Marvel.value">
{{ h.name}} - {{h.comic}}
</div>
<div ng-repeat="h in comics.DC.items" ng-show="comics.DC.value">
{{ h.name}} - {{h.comic}}
</div>
Plnk3 - the better one
Possible solution 1: Inline
You could actually save a reference to the filtered results in a variable: h in filtered.marvel = (heroes | filter:{comic:'Marvel'}), which you could use like so: filtered.marvel.length.
See: Plunkr
Possible solution 2: In the controller
You could also move this code to your controller:
$scope.filteredHeroes.marvel = $filter('filter')($scope.heroes, {comic:'Marvel'});
, which you could use by ng-repeat="hero in filteredHeroes.marvel"
and {{filteredHeroes.marvel.length}}
(Don't forget to add $filter as a controller dependency)
See: Plunkr
To find the count objects, I use <scope_obj>.length in the .html template.
Here's my controller:
conciergeControllers.controller('GuestMsgPreviewCtrl', ['$scope', 'GuestMessages',
function($scope, GuestMessages) {
$scope.guests = GuestMessages.query();
}]);
And template (each guest object has a messages attribute that is an array object, so .length returns the number of nested message objects:
<ul ng-repeat="guest in guests">
<li>[[ guest.messages.length ]]</li>
</ul>

angularjs filter on nested objects

I am quite new with angularjs so I am not sure if what I am trying to do is the right way. Basically I want to display in my page a nested object, and a filter, this way the user can easily type keywords on an input and the content get filtered, to only display the recods that get found by the filter
However I notice that the filter gets the whole parent object and i was expecting only display the record, so with the following code, if i search for Japan it will display Sydney, Melbourne and los angeles.
Javascript
<script type="text/javascript">
var demoApp = angular.module('demoApp',['ngSanitize']);
demoApp.controller('simpleC',['$scope', function ($scope){
$scope.info = [
{name: 'Documents',links: [
{linkname:'title1',linknamesublink:[
{namesublink:'document 1', description: 'Sydney'},
{namesublink:'document 2', description: 'Tokyo <b>Japan</b>'}
]},
{linkname:'title2',linknamesublink:[
{namesublink:'document 3', description: 'Melbourne'},
{namesublink:'document 4', description: 'Los Angeles'}
]}
]},
{name: 'Video',links: [
{linkname:'title1',linknamesublink:[
{namesublink:'video 1', description: 'California'},
{namesublink:'video 2', description: 'San Francisco <b> USA</b>'}
]},
{linkname:'title2',linknamesublink:[
{namesublink:'video 3', description: 'South America'},
{namesublink:'video 4', description: 'Northern <b>Europe</b>'}
]},
{linkname:'title3',linknamesublink:[
{namesublink:'video name 5', description: 'Africa'},
{namesublink:'video name 6', description: 'Bangkok <b>Thailand</b>'}
]}
]},
];
}]);
</script>
html:
<div class="container" ng-app="demoApp">
<br /><input type="text" class="form-control" ng-model="search" >
<div ng-controller="simpleC">
<div ng-repeat="i in info | filter:search" >
{{i.name}} :
<div ng-repeat="link in i.links">
{{link.linkname}}
<div ng-repeat="sublink in link.linknamesublink">
{{sublink.namesublink}}: <!--{{sublink.description}}-->
<span ng-bind-html="sublink.description"></span>
</div>
</div>
</div>
</div>
</div>
Follow this example
http://www.whatibroke.com/?p=857
You can use filter in ng-repeat to filter items by property or write a filter yourself.
The author want to filter postcode property by the value of searc_postcode. So, he wrote //filter {postcode : search_postcode} //
Ps. Sorry, I misinterpret your question.
Write your own filter function. This way, you have total control over what is returned. https://docs.angularjs.org/api/ng/filter/filter
module.filter('searchfilter', function() {
return function(searchexpr) {
var filteredList = ['my', 'filtered', 'result'];//implement yourself
return filteredList;
};
});
and use searchfilter instead of filter in your markup.
Or, try to use a filter function as described here: http://fdietz.github.io/recipes-with-angular-js/common-user-interface-patterns/filtering-and-sorting-a-list.html (as far as I can tell, you didn't define a search function, i.e. the first agument for the filter expression).
Modified :
If you want to filter the objects based on any field(say "name"), you can use the below format
<div ng-repeat="i in info" ng-if="search==i.name">

How to handle selected/unselected ng-class to manage a tabbed popover

My code is as follows (the fiddle):
<div ng-controller="myCtrl">
<div ng-model="currentTab" ng-init="currentTab='Tab1'"/>
<div ng-init="popovers = [
{ name: 'Popover1',
displayName: 'Pop over with two tabs',
tabs: [
{ name: 'Tab1',
displayName: 'First tab',
description: ['First tab description']
},
{ name: 'Tab2',
displayName: 'Second tab',
description: ['Second tab description']
}
]
}
]"/>
<b>Tabs in popover</b>
<div
class="popover"
ng-repeat="p in popovers"
>
Popover name: {{p.displayName}}
<div ng-repeat="t in p.tabs"
class="tab"
ng-class="currentTab==t.name?'selected':''"
ng-click="currentTab=t.name"
>
{{t.name}}
</div>
<div ng-repeat="t in p.tabs"
class="tabContent"
ng-class="currentTab==t.name?'selected':''"
>
<p>{{t.displayName}}</p>
</div>
</div>
</div>
There is something I don't get which make the code not working perfectly, as the selected class name is never removed as one click on the tab.
When you want to modify a variable of your parent scope from within a ng-repeat you need to use $parent.currentTab.
Updated Fiddle

Resources