Switch between displayed data - angularjs

I've started looking at AngularJS a few hours ago so I'm settling into how things work. As part of a basic example, I'm trying to figure out how I switch between displayed data in a table.
At the moment, I've got the following as my basic app;
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
</head>
<body>
<div ng-app="applicationMain" ng-controller="controllers.app.main">
<table>
<tr ng-repeat="item in items">
<button>Toggle</button>
<td>{{item.name}}<td>
</tr>
</table>
</div>
</body>
<script>
var controllers = {
app : {
main : function($scope){
var s = $scope;
s.items = [
{
name : "Pizza",
price : 100
},
{
name : "Burger",
price : 45
},
{
name : "Kebab",
price : 85
}
];
}
}
}
var app = angular.module("applicationMain", []);
app.controller('controllers.app.main', controllers.app.main);
</script>
</html>
Fairly simple. Scoped array of objects with name and price fields, where the name of each is displayed in a table using ng-repeat
What I'd like to do is when I click the Toggle button, it switches between displaying the data of item.name to displaying data of item.price.
Is this something that can be done within the angular expression of the <TD> tags, or would a function be the way to go?
If I was using regular old JS for example, I might do something like this;
var activeField = item.name;
if (activeField == item.name){
activeField = item.price
} else {
activeField = item.name
}
However, I tried something similar by creating a 'switchField' function in my controller, but Angular reports that 'item is not defined' (essentially a scoping issue) even when defining it at $scope.item.price and $scope.item.name respectively.

You can do something like this, using ngClick, ngShow and ngHide:
<table>
<tr ng-repeat="item in items">
<button ng-click="toggleIt()">Toggle</button>
<td ng-show="toggle">{{item.name}}<td>
<td ng-hide="toggle">{{item.price}}<td>
</tr>
</table>
And add this to the controller:
s.toggle = true;
s.toggleIt = function() {
s.toggle = !s.toggle;
}

Related

Filtering ng-repeat result set is not working

I tried to create a textbox based filtering of a ng-repeat result. Though there is no errors listed, the filtering is not working. What is missing here?
Updated code after making following change:
<tbody ng-repeat="objReview in reviewsList | myCustomFilter:criteria" >
The filter is gettiing called two times for each row. How to make it call only once?
Code
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular-resource.js"></script>
<style type="text/CSS">
table
{border-collapse: collapse;width: 100%;}
th, td
{text-align: left;padding: 8px;}
tr:nth-child(even)
{background-color: #f2f2f2;}
th
{background-color: #4CAF50;color: white;}
</style>
<script type="text/javascript">
//defining module
var app = angular.module('myApp', ['ngResource']);
//defining factory
app.factory('reviewsFactory', function ($resource) {
return $resource('https://api.mlab.com/api/1/databases/humanresource/collections/Reviews',
{ apiKey: 'myKey' }
);
});
//defining controller
app.controller('myController', function ($scope, reviewsFactory)
{
$scope.criteria = "";
$scope.reviewsList = reviewsFactory.query();
});
app.filter('myCustomFilter', function ()
{
return function (input, criteria)
{
var output = [];
if (!criteria || criteria==="")
{
output = input;
}
else
{
angular.forEach(input, function (item)
{
alert(item.name);
//alert(item.name.indexOf(criteria));
//If name starts with the criteria
if (item.name.indexOf(criteria) == 0)
{
output.push(item)
}
});
}
return output;
}
});
</script>
</head>
<body ng-app="myApp">
<div ng-controller="myController">
<label>
SEARCH FOR: <input type="text" ng-model="criteria">
</label>
<table>
<thead>
<tr>
<th>Name</th>
<th>Review Date</th>
</tr>
</thead>
<tbody ng-repeat="objReview in reviewsList | myCustomFilter:criteria" >
<tr>
<td>{{objReview.name}}</td>
<td>{{objReview.createdOnDate}}</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
Further Reading
Is this normal for AngularJs filtering
How does data binding work in AngularJS?
As noted in the comments by Lex you just need to get rid of the prefix 'filter', so change
<tbody ng-repeat="objReview in reviewsList | filter:myCustomFilter:criteria" >
to
<tbody ng-repeat="objReview in reviewsList | myCustomFilter:criteria" >
In addition you should set an initial value for your controller's property criteria as otherwise your initial list will be empty as your filter will not match anything due to the comparison operator === which takes the operands' types into account and critiera will be undefined until you first enter something in your textbox.
app.controller('myController', function ($scope, reviewsFactory)
{
$scope.criteria = '';
$scope.reviewsList = reviewsFactory.data();
});

Angular update object in array

I wanna update an object within an objects array. Is there another possibility than iterating over all items and update the one which is matching? Current code looks like the following:
angular.module('app').controller('MyController', function($scope) {
$scope.object = {
name: 'test',
objects: [
{id: 1, name: 'test1'},
{id: 2, name: 'test2'}
]
};
$scope.update = function(id, data) {
var objects = $scope.object.objects;
for (var i = 0; i < objects.length; i++) {
if (objects[i].id === id) {
objects[i] = data;
break;
}
}
}
});
There are several ways to do that. Your situation is not very clear.
-> You can pass index instead of id. Then, your update function will be like:
$scope.update = function(index, data) {
$scope.object.objects[index] = data;
};
-> You can use ng-repeat on your view and bind object properties to input elements.
<div ng-repeat="item in object.objects">
ID: <input ng-model="item.id" /> <br/>
Name: <input ng-model="item.name" /> <br/>
</div>
Filters that help in finding the element from the array, can also be used to update the element in the array directly.
In the code below [0] --> is the object accessed directly.
Plunker Demo
$filter('filter')($scope.model, {firstName: selected})[0]
Pass the item to your update method. Take a look at sample bellow.
function MyCtrl($scope) {
$scope.items =
[
{name: 'obj1', info: {text: 'some extra info for obj1', show: true}},
{name: 'obj2', info: {text: 'some extra info for obj2', show: false}},
];
$scope.updateName = function(item, newName){
item.name = newName;
}
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app>
<table ng-controller="MyCtrl" class="table table-hover table-striped">
<tr ng-repeat="x in items">
<td> {{ x.name }}</td>
<td>
Update
<div ng-show="showUpdate" ><input type="text" ng-model="someNewName"> <input type="button" value="update" ng-click="updateName(x, someNewName); showUpdate = false;"></div>
</td>
</tr>
</table>
</body>
Going off your plunker, I would do this:
Change
Edit
to be
Edit
Then use the array index within your $scope.selectSubObject method to directly access your desired element. Something like this:
$scope.selectSubObject = function(idx) {
$scope.selectedSubObject = angular.copy(
$scope.selectedMainObject.subObjects[idx]
);
};
If however, you only have the id to go off of, then you can use the angular filterService to filter on the id that you want. But this will still do a loop and iterate over the array in the background.
See angularjs documentation for ngrepeat to see the variables that it exposes.

Two way binding is not working with angular js

I have the html code like this:
<div ng-controller="MainCtrl">
<tr data-ng-repeat="element in awesomeThings">
<div ng-if="$even">
<td ng-click="getServiceDetails(element)">
<a href="#">
{{element['serviceDetail']['name']}}
</a>
</td>
</div>
</tr>
//after some html
<span >{{publicName}}</span>
</div>
My controller looks like this:
angular.module('dcWithAngularApp')
.controller('MainCtrl', function ($scope,$http,test) {
$scope.awesomeThings = {"serviceDetail":{"name":"batman"}}
$scope.getServiceDetails = function(serviceDetails)
{
console.log('Called')
$scope.publicName = serviceDetails.name
}
});
After clicking on the td tag, the span text are not changing! Even though me changing the publicName in the current scope!
Where I'm making the mistake?
You don't have a name property in your $scope.awesomeThings array.
Here I added some animals for you.
angular.module('dcWithAngularApp')
.controller('MainCtrl', function ($scope,$http,test) {
$scope.awesomeThings = [
{ name: "Dog" },
{ name: "Cat" }
];
$scope.getServiceDetails = function(serviceDetails)
{
console.log('Called')
$scope.publicName = serviceDetails.name
}
});
Edit:
I put together a JSFiddle that solves your edit.
http://jsfiddle.net/HB7LU/7191/
$scope.awesomeThings is an array of string so element does not have a 'name' property.
You also have a missing ; after your console.log which may be stopping the code from running.
It may help you to log what the object is.
<div ng-controller="MainCtrl">
<tr data-ng-repeat="element in awesomeThings">
<div ng-if="$even">
<td ng-click="getServiceDetails(element)">
<a href="#">
{{element.name}}
</a>
</td>
</div>
</tr>
//after some html
<span >{{publicName}}</span>
</div>
angular.module('dcWithAngularApp')
.controller('MainCtrl', function ($scope,$http,test) {
$scope.awesomeThings = {"serviceDetail":{"name":"batman"}};
$scope.getServiceDetails = function(serviceDetails)
{
console.log(serviceDetails);
$scope.publicName = serviceDetails.name;
}
});
Your main issue is this:
$scope.publicName = serviceDetails.name
It needs to be
$scope.publicName = serviceDetails;
$scope.awesomeThings is an array of strings, not objects. You're ng-repeating through the string array and passing the string to your getServiceDetails method, so 'serviceDetails' is just a string.
As an unrelated warning, I see you're using tr inside a div. The tr should be inside a table, thead, or tbody.

Calculate total over repeated cart item totals with angular

I've a simplified shopping cart like the following with a controller for each cart item:
<!DOCTYPE HTML>
<html ng-app="cart">
<div ng-controller="cartCtrl">
<table>
<tr><th>qty</th><th>prize</th><th>total</th></tr>
<tr ng-repeat="item in cartItems" ng-controller="cartItemCtrl">
<td>
<input ng-model="item.qty"/>
</td>
<td>
<input ng-model="item.prize" />
</td>
<td>
{{total}}
</td>
</tr>
</table>
total: {{cartTotal}}
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js"></script>
<script>
(function(){
var cart = angular.module('cart', []);
cart.controller('cartCtrl', ['$scope', function($scope){
$scope.cartItems = [{},{},{},{}];
}]);
cart.controller('cartItemCtrl', ['$scope', function($scope){
$scope.$watchCollection('item', function(){
$scope.total = $scope.item.qty * $scope.item.prize;
});
}]);
}());
</script>
</html>
I couldn't make it work in JSFiddle: http://jsfiddle.net/XY3n8/1/
Now I want to to calculate the cart total but I do not want to recalculate the item totals. Instead I want to reuse the already calculated item totals. How? (In my use case the item total calculation is a bit more complex.)
You need to do a "deep" watch on the collection and object to get sub-totals and totals. $watchCollection is better for knowing when things get added/removed. $watch will look for changes in the existing object(s).
For encapsulating the item's total, there are several ways to do this, but I'd probably create an Item model (probably a better name) and inject it via a factory. It removes the need for one of the controllers in this example, but requires you to create a module (which is best practice anyway)
How about this?
var ShoppingCart = angular.module('shoppingCart', []);
ShoppingCart.factory('Item', function() {
function Item() {
this.total = function() {
return (this.qty * this.prize) || 0;
}
}
return Item;
});
ShoppingCart.controller('cartCtrl', function($scope, Item) {
$scope.cartItems = [new Item(), new Item(), new Item(), new Item()];
$scope.$watch('cartItems', function() {
var cartTotal = 0;
$scope.cartItems.forEach(function(item) {
cartTotal += item.total();
});
$scope.cartTotal = cartTotal;
}, true);
});
And the HTML updates slightly. You reference the module name in the ng-app, get rid of the sub-controller, and reference item.total() directly in the view.
<div ng-app="shoppingCart" ng:controller="cartCtrl">
<table>
<tr><th>qty</th><th>prize</th><th>total</th></tr>
<tr ng:repeat="item in cartItems">
<td>
<input ng:model="item.qty"/>
</td>
<td>
<input ng:model="item.prize" />
</td>
<td>
{{item.total()}}
</td>
</tr>
</table>
total: {{cartTotal}}
</div>
Here is a working codepen.
This is an approach using the ng-init directive to extend the model and do the calculations.
http://www.ozkary.com/2015/06/angularjs-calculate-totals-using.html

How to default a table search results to hidden with AngularJS filters?

In the following Angularjs snippet, an entire table is shown by default and gets filtered down as you start typing.
What would be best practice to change it to show no results by default and only start showing results after, say, at least 3 results match the search query?
Bonus question, how would you go about only displaying results if a minimum of 2 characters have been entered?
Html:
<div ng-app="myApp">
<div ng-controller="PeopleCtrl">
<input type="text" ng-model="search.$">
<table>
<tr ng-repeat="person in population.sample | filter:search">
<td>{{person.name}}</td>
<td>{{person.job}}</td>
</tr>
</table>
</div>
</div>
Main.js:
var myApp = angular.module('myApp', []);
myApp.factory('Population', function () {
var Population = {};
Population.sample = [
{
name: "Bob",
job: "Truck driver"
}
// etc.
];
return Population;
});
function PeopleCtrl($scope, Population) {
$scope.people = Population;
}
You can do all of that in your markup, actually... here's a plunk to demonstrate
And here's the change in your markup:
<input type="text" ng-model="search">
<table ng-show="(filteredData = (population.sample | filter:search)) && filteredData.length >= 3 && search && search.length >= 2">
<tr ng-repeat="person in filteredData">
<td>{{person.name}}</td>
<td>{{person.job}}</td>
</tr>
</table>
EDIT: changed my answer to reflect your requests.

Resources