AngularJS - ng-repeat show one item at a time - angularjs

looking for some ideas here. i have a meal plan object that contains an array of meals. only one meal can be set as primary at a time but i want the user to be able to cycle through the array of meals and mark a meal as primary. i am stuck trying to figure out if ngrepeat makes sense here or ngswitch or ngshow. any thoughts or samples would be highly appreciated!
I have tried multiple approaches with no luck.
thanks

You could cycle through the meals by index of the meal and have a button to choose the meal like this:
http://jsfiddle.net/c6RZK/
var app = angular.module('mealsApp',[]);
app.controller('MealsCtrl',function($scope) {
$scope.meals = [
{name:'Meatloaf'},
{name:'Tacos'},
{name:'Spaghetti'}
];
$scope.meal_index = 0;
$scope.meal = {};
$scope.next = function() {
if ($scope.meal_index >= $scope.meals.length -1) {
$scope.meal_index = 0;
}
else {
$scope.meal_index ++;
}
};
$scope.choose = function(meal) {
$scope.meal = meal;
}
});
HTML
<div ng-app="mealsApp" ng-controller="MealsCtrl">
<div ng-repeat="m in meals">
<div ng-if="meal_index == $index">
<strong>{{m.name}}</strong>
<button ng-click="choose(m)">Choose</button>
</div>
</div>
<hr>
<button ng-click="next()">Next</button>
<hr>Your Choice: {{meal.name}}
</div>

You could just attach a property to the plan, with a flag that says whether or not it's the primary plan.
Here's a sample implementation:
$scope.plans = [{name:"One"}, {name:"Two"}, {name:"Three"}];
$scope.selectPlan = function(plan) {
for(var i = 0, l = $scope.plans.length; i < l; i++) {
$scope.plans[i].primary = false;
if($scope.plans[i] === plan) {
$scope.plans[i].primary = true;
}
}
};
HTML:
<ul>
<li ng-click="selectPlan(plan)" ng-repeat="plan in plans" ng-class="{primary: plan.primary}"><a href>{{plan.name}}</a></li>
</ul>
If you'd rather not attach properties you could use something like a selected index property on your controller.

Related

How to "connect" 2 variables in Angular by some value

I have 2 variables in controller:
$scope.first = [
{ id="11", nameF="aaaa1" },
{ id="12", nameF="bbbb1" }
]
$scope.second = [
{ id="21", nameS="aaaa2", idFirst="11" },
{ id="22", nameS="bbbb2", idFirst="12" },
{ id="23", nameS="cccc2", idFirst="12" }
]
In a template I have ngRepeat for variable second:
<div ng-repeat="item in second>
<div>{{item.nameS}}</div>
<div>{{item.idFirst}}</div>
</div>
For every item.idFirst I would like to write out matching nameF instead. What is the best practice to achieve that? I can't seem to figure out a simple way to do it, but suppose there has to be one. Thanx!
You can use custom filter if you don't want to create one single object holding the expected structure.
HTML :
<div ng-repeat="item in second">
<div>{{item.nameS}}</div>
<div>{{item.idFirst | getMatchName:first}}</div>
</div>
JS:
.filter('getMatchName', function() {
return function(strName, arrFirst) {
arrFirst.forEach(function(val, key) {
if (val.id == strName) {
strName = val.nameF;
}
})
return strName;
}
})
Here is working plunker
If I understand correctly,
$scope.getNameF = function(idFirst){
for(var i = 0; i < $scope.first.length; i++){
if($scope.first[i].id === idFirst){
return $scope.first[i].nameF;
}
}
return undefined;
}
<div ng-repeat="item in second">
<div>{{item.nameS}}</div>
<div>{{getNameF(item.idFirst)}}</div>
</div>
EDIT
Also you can prepare data before rendering:
for(var i = 0; i < $scope.second.length; i++){
$scope.second[i].nameF = getNameF($scope.second[i].idFirst);
}
<div ng-repeat="item in second">
<div>{{item.nameS}}</div>
<div>{{item.nameF}}</div>
</div>
You can think of your template logic a bit like code, calling values from other variables: yourValue[another.value].nameF
Change your code to this and it should work
<div ng-repeat="item in second>
<div>{{item.nameS}}</div>
<div>{{first[item.idFirst].nameF}}</div>
</div>

How to apply dynamic filters in AngularJS

I'm starting to learn AngularJS now and I have some issues with filters.
I need to apply two types of filters and I can't figure out how.
I have a device list JSON that looks like this:
[{
"ID": 1,
"Name": "Device 1",
"Price": 1998.92,
"Colors": [{
"ColorCode": "Red",
"ColorName": "#FF0000"
},
{
"ColorCode": "Green",
"ColorName": "#2EFE2E"
}],
"Type": {
"TypeID": 1,
"TypeName": "Mobile device"
},
"Company": {
"CompanyID": 1,
"CompanyName": "Alcatel"
}
}]
I display the list like this:
<div ng-repeat="device in devices | filter:companyFilters | filter:colorFilters">
<span>{{device.Company.CompanyID}}</span> // 1
<span>{{device.Company.CompanyName}}</span> // Google
<span>{{device.Name}}</span> // Nexus 6P
</div>
I have some filters that I applied but there are two filters that I can't understand how to apply.
Filter 1:
A checkbox list of companies that filters the items by the selected
companies.
Filter 2:
A color filter that when clicking on a color will filter the devices
that has that color
For the company filter I have this checkbox list:
<div ng-repeat="company in deviceCompanies">
<input type="checkbox" data-ng-model="companyFilters" id="{{company.CompanyID}}" data-ng-true-value='{{company.CompanyID}}' data-ng-false-value='' />
<label for="{{company.CompanyID}}">{{company.CompanyName}}</label>
</div>
And on the controller side I have this:
$scope.companyFilters = [];
For the color filter I have this:
<div>
<a ng-click="???">All</a>
<div ng-repeat="color in deviceColors" style="display:inline-block; margin-right:10px;">
<div style="width:20px;height:20px;background-color:{{color.ColorCode}}"></div>
<a ng-model="selColor" data-ng="color.ColorCode" ng-click="colorFilters">{{color.ColorName}}</a>
</div>
</div>
And on the controller:
$scope.colorFilters = function (device) {
if (!$scope.selColor)
return true;
for (var i = 0; i < device.Colors.length; i++) {
if (device.Colors[i].ColorCode == $scope.selColor)
return true;
}
return false;
};
But it doesn't work...
Can anyone please tell me how to apply these filters ?
Since you are using ng-repeat which creates its own scope, anything you do in ng-repeat will not get recognized on the controller scope. Using a tool like ng-inspector or batarang will illustrate this.
I recommend using controllerAs Syntax, in your controller add.
angular.module('myModule').controller('CustomFilterController', function() {
var vm = this;
this.devices = //your list of data
this.companyFilters = [];
this.colorFilters = //your function
}
On your view declare your controller like this:
<div ng-controller='CustomFilterController as custom'>
(Note the value after as can be whatever you want it to be)
Then reference anything on that controller as custom.ThingOnController
EX:
<div ng-repeat="device in custom.devices | filter:custom.companyFilters | filter:custom.colorFilters">
After trying some workarounds, this is what I came up with:
<div ng-repeat="company in deviceCompanies">
<!--the ng-click will call a function that updated an array of values-->
<input type="checkbox" id="{{company.CompanyID}}" ng-click="selectCompany(company.CompanyID)">
<label for="{{company.CompanyID}}">{{company.CompanyName}}</label>
</div>
And the controller part is so simple:
$scope.selectedCompanies = [];
//when the array is upted the filter function will also launch
$scope.selectCompany = function (companyId) {
var i = $.inArray(companyId, $scope.selectedCompanies);
if (i > -1) {
$scope.selectedCompanies.splice(i, 1);
} else {
$scope.selectedCompanies.push(companyId);
}
}
$scope.companyFilter = function (device) {
if ($scope.selectedCompanies.length > 0) {
if ($.inArray(device.Company.CompanyID, $scope.selectedCompanies) < 0)
return;
}
return device;
}
Same goes to the colors filter:
<div>
<a ng-click="selectColor()">All</a>
<div ng-repeat="color in deviceColors" style="display:inline-block; margin-right:10px;">
<div style="width:20px;height:20px;background-color:{{color.ColorCode}}"></div>
<a ng-click="selectColor(color.ColorCode)">{{color.ColorName}}</a>
</div>
</div>
And controller part:
$scope.selectedColor;
$scope.selectColor = function (colorCode) {
$scope.selectedColor = colorCode;
}
$scope.colorFilters = function (device) {
if (!$scope.selectedColor)
return true;
for (var i = 0; i < device.Colors.length; i++) {
if (device.Colors[i].ColorCode == $scope.selectedColor)
return true;
}
return false;
};
And finally, applying the filters:
<div ng-repeat="device in devices | filter:companyFilter | filter:colorFilters">
...
</div>
You can have a look here to see how it works
I think the color filter can be more elegant, but, for now, this does the trick.
If I'll come up with a better solution I'll post it here.

ng hide/show based on list values

I need to hide the "Current insurance cover" when coverAmt of all the covers in the list are zero or negative value. If any one coverAmt in the list is positive value then I need show the "Current insurance cover" only once.
I tried with something like this, but now no luck..!
<li ng-repeat="cover in accSummary.response.covers">
<div class="account-detail-row col-xs-12 col-sm-12 col-md-12 padding-left12 padding-right12 padding-top24 padding-bottom12">
<h4 class="font-16 fontwt-400" ng-hide="cover.coverAmt <= 0">Current insurance cover</h4>
</div>
</li>
can anybody please help me on this..! thanks.
You have to calculate this in your controller as below:
app.controller("cover", function($scope){
$scope.covers = [{coverAmt: 10},{coverAmt: 0},{coverAmt: -1}];
$scope.showHeading = function(){
var flag = false;
for(var i=0; i<$scope.covers.length; i++)
{
if($scope.covers[i].coverAmount > 0)
{
flag = true;
break;
}
}
return flag;
};
});
HTML will be
<h4 class="font-16 fontwt-400" ng-show="showHeading();">Current insurance cover</h4>
Try using ```ng-if`` to control the visibility of the Current Insurance Cover. But on the init of the ng-repeat, run a call to loop through the array to test your case.
Below is a sample of what this would look like:
HTML:
<li ng-repeat="cover in accSummary.response.covers" ng-init="checkCover()">
<div class="account-detail-row col-xs-12 col-sm-12 col-md-12 padding-left12 padding-right12 padding-top24 padding-bottom12">
<h4 class="font-16 fontwt-400" ng-if="visible">Current insurance cover</h4>
</div>
</li>
JS:
$scope.visible = false;
$scope.checkCover = function() {
angular.forEach(accSummary.response.covers, function(cover) {
if(cover.coverAmt > 0) { $scope.visible = true; return true; }
}
return false;
}
Firstly if you have the h4 Current insurance cover, within the ng-repeat. If you have multiple positive values, the header will appear multiple times
As for the calculation itself... Rather do that in your controller, then do something like:
<li ng-repeat="cover in accSummary.response.covers">
<div class="account-detail-row">
{{ cover.Something }}
</div>
</li>
<h4 ng-hide="isCurrent">Current insurance cover</h4>
Thus in the success event of the ajax request / promise you could do this:
var total = 0;
$http({ // or whatever request mechanism you're using
url: someURL,
method: "POST",
data: "{}",
headers: { 'Content-Type': 'application/json' }
})
.success(function (data) {
angular.forEach(data.accSummary, function(value, key) {
total += data.accSummary["coverAmt"]
})
})
.finally(function() {
$scope.isCurrent = total <= 0;
})
Or you could create a function and then pass the whole collection through from the view.
Your calculation function could look something like this:
$scope.isCurrent = function(covers) {
var total = 0;
for(var i = 0; i < covers.length; i++){
var cover = covers[i];
total += cover.coverAmt;
}
return total <= 0;
}
Then the usage would be usage:
<h4 ng-hide="isCurrent(accSummary.response.covers)">Current insurance cover</h4>

AngularJS cant get category "All" while filtering

I started to play with angular and I am trying to write a simple app that consists of categories containing items. ( I am trying to implement a tutorial for my needs )
Now I am trying to add a filter to select items by categories. I can filter them unless I choose All categories. I cant get all the categories.
I have edges service :
angular.module('swFrontApp')
.controller('EdgesController', function ($scope, edges,categories) {
$scope.edges = edges.query();
$scope.categories = categories.query();
$scope.filterBy = {
search: '',
category: $scope.categories[0]
};
var selectedEdge = null;
$scope.selectEdge = function(edge) {
selectedEdge = (selectedEdge === edge) ? null : edge;
};
$scope.isSelected = function(edge) {
return edge === selectedEdge;
};
$scope.displayRequirements = function(reqs) {
var result = '';
for ( var i = 0; i < reqs.length; i ++) {
if (result !== '' ) { result += ', '}
if (reqs[i].name) {
result += reqs[i].name+ ' ';
}
result += reqs[i].value;
}
return result;
};
});
and I try to filter them using :
angular.module('swFrontApp').filter('edges', function() {
return function(edges, filterBy) {
return edges.filter( function( element, index, array ) {
return element.category.name === filterBy.category.name;
});
};
} );
Here is my html to get edges with categories filter
<select
name="category"
ng-model="filterBy.category"
ng-options="c.name for c in categories"
class="form-control"></select>
<ul>
<li ng-repeat-start="edge in edges | filter:{name: filterBy.search}| edges: filterBy " ng-click="selectEdge(edge)">
<span class="label label-default">{{ edge.category.name }}</span>
{{edge.name}}
<span class="text-muted">({{ displayRequirements(edge.requirements) }})</span>
</li>
<li ng-repeat-end ng-show="isSelected(edge)">
{{edge.description}}
</li>
</ul>
I formed My Plunker link is here.
Thanks
It doesn't work because of the category.name attribute. In your categoriesService.js you return collection where name equals to All. But if you look into EdgesService file, you'll see that there is no such option as 'All'. So this comparison in script.js file (in your filter)
return element.category.name === filterBy.category.name;
will always return false when filterby.category.name equals to 'All'.
The way to fix it is to change it to something like this:
return element.category.name === filterBy.category.name || filterBy.category.name === 'All';
This way it will always return true if 'All' category is selected.
Also later in the course rank option will be introduced as well. You can browse the code for that project here: https://github.com/Remchi/sw-front
Hope that helps. :)

Filtering a nested ng-repeat: Hide parents that don't have children

I want to make some kind of project list from a JSON file. The data structure (year, month, project) looks like this:
[{
"name": "2013",
"months": [{
"name": "May 2013",
"projects": [{
"name": "2013-05-09 Project A"
}, {
"name": "2013-05-14 Project B"
}, { ... }]
}, { ... }]
}, { ... }]
I'm displaying all data using a nested ng-repeat and make it searchable by a filter bound to the query from an input box.
<input type="search" ng-model="query" placeholder="Suchen..." />
<div class="year" ng-repeat="year in data | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.months | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in month.projects | filter:query | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
If I type "Project B" now, all the empty parent elements are still visible. How can I hide them? I tried some ng-show tricks, but the main problem seems so be, that I don't have access to any information about the parents filtered state.
Here is a fiddle to demonstrate my problem: http://jsfiddle.net/stekhn/y3ft0cwn/7/
You basically have to filter the months to only keep the ones having at least one filtered project, and you also have to filter the years to only keep those having at least one filtered month.
This can be easily achieved using the following code:
function MainCtrl($scope, $filter) {
$scope.query = '';
$scope.monthHasVisibleProject = function(month) {
return $filter('filter')(month.children, $scope.query).length > 0;
};
$scope.yearHasVisibleMonth = function(year) {
return $filter('filter')(year.children, $scope.monthHasVisibleProject).length > 0;
};
and in the view:
<div class="year" ng-repeat="year in data | filter:yearHasVisibleMonth | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.children | filter:monthHasVisibleProject | orderBy:sortMonth:true">
This is quite inefficient though, since to know if a year is accepted, you filter all its months, and for each month, you filter all its projects. So, unless the performance is good enough for your amount of data, you should probably apply the same principle but by persisting the accepted/rejected state of each object (project, then month, then year) every time the query is modified.
I think that the best way to go is to implement a custom function in order to update a custom Array with the filtered data whenever the query changes. Like this:
$scope.query = '';
$scope.filteredData= angular.copy($scope.data);
$scope.updateFilteredData = function(newVal){
var filtered = angular.copy($scope.data);
filtered = filtered.map(function(year){
year.children=year.children.map(function(month){
month.children = $filter('filter')(month.children,newVal);
return month;
});
return year;
});
$scope.filteredData = filtered.filter(function(year){
year.children= year.children.filter(function(month){
return month.children.length>0;
});
return year.children.length>0;
});
}
And then your view will look like this:
<input type="search" ng-model="query" ng-change="updateFilteredData(query)"
placeholder="Search..." />
<div class="year" ng-repeat="year in filteredData | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.children | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in month.children | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
Example
Why not a custom $filter for this?
Efficiency: the nature of the $diggest cycle would make it much less efficient. The only problem is that this solution won't be as easy to re-use as a custom $filter would. However, that custom $filter wouldn't be very reusable either, since its logic would be very dependent on this concrete data structure.
IE8 Support
If you need this to work on IE8 you will have to either use jQuery to replace the filter and map functions or to ensure that those functions are defined, like this:
(BTW: if you need IE8 support there is absolutely nothing wrong with using jQuery for these kind of things.)
filter:
if (!Array.prototype.filter) {
Array.prototype.filter = function(fun/*, thisArg*/) {
'use strict';
if (this === void 0 || this === null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== 'function') {
throw new TypeError();
}
var res = [];
var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i];
if (fun.call(thisArg, val, i, t)) {
res.push(val);
}
}
}
return res;
};
}
map
if (!Array.prototype.map) {
Array.prototype.map = function(callback, thisArg) {
var T, A, k;
if (this == null) {
throw new TypeError(" this is null or not defined");
}
var O = Object(this);
var len = O.length >>> 0;
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
if (thisArg) {
T = thisArg;
}
A = new Array(len);
k = 0;
while(k < len) {
var kValue, mappedValue;
if (k in O) {
kValue = O[ k ];
mappedValue = callback.call(T, kValue, k, O);
A[ k ] = mappedValue;
}
k++;
}
return A;
};
}
Acknowledgement
I want to thank JB Nizet for his feedback.
For those who are interested: Yesterday I found another approach for solving this problem, which strikes me as rather inefficient. The functions gets called for every child again while typing the query. Not nearly as nice as Josep's solution.
function MainCtrl($scope) {
$scope.query = '';
$scope.searchString = function () {
return function (item) {
var string = JSON.stringify(item).toLowerCase();
var words = $scope.query.toLowerCase();
if (words) {
var filterBy = words.split(/\s+/);
if (!filterBy.length) {
return true;
}
} else {
return true;
}
return filterBy.every(function (word) {
var exists = string.indexOf(word);
if(exists !== -1){
return true;
}
});
};
};
};
And in the view:
<div class="year" ng-repeat="year in data | filter:searchString() | orderBy:'name':true">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.children | filter:searchString() | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in month.children | filter:searchString() | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>
Here is the fiddle: http://jsfiddle.net/stekhn/stv55sxg/1/
Doesn't this work? Using a filtered variable and checking the length of it..
<input type="search" ng-model="query" placeholder="Suchen..." />
<div class="year" ng-repeat="year in data | orderBy:'name':true" ng-show="filtered.length != 0">
<h1>{{year.name}}</h1>
<div class="month" ng-repeat="month in year.months | orderBy:sortMonth:true">
<h3>{{month.name}}</h3>
<div class="project" ng-repeat="project in filtered = (month.projects | filter:query) | orderBy:'name'">
<p>{{project.name}}</p>
</div>
</div>
</div>

Resources