Ng-repeat doesn't works with localStorage - angularjs

Something weird is happening with my localStorage. Data is saved in my localStorage but is never displayed in ng-repeat. However, when the data is less than 2 items, in that case, it is displayed on the screen. When it is greater than 2 it is not shown. This is my controller code
$scope.saved = localStorage.getItem('todos');
if($scope.saved != null ) {
$scope.invoice = { items: JSON.parse($scope.saved) };
} else {
$scope.invoice = { `enter code here`
items : [{
qty: 10,
description: 'item',
cost: 9.95}]
};
}
And this is my addition code:
$scope.addItem = function(x) {
$scope.invoice.push({
qty: 1,
description: x.cakename,
cost: x.id
});
localStorage.clear();
localStorage.setItem('todos', JSON.stringify($scope.invoice));
};
This is table code:
<div>
<table class="table" style="margin-top:50px">
<tr>
<th>Name</th>
<th>Qty</th>
<th>Cost</th>
<th>Total</th>
<th></th>
</tr>
<tr ng-repeat="item in invoice">
<th>#{{ item.description }}</th>
<th>#{{ item.qty }}</th>
<th>#{{ item.cost }}</th>
<th>#{{ item.qty * item.cost }}</th>
<td>[<a href ng:click="removeItem($index)">X</a>]</td>
</tr>
<tr>
<td></td>
<td></td>
<td>Total:</td>
<td> #{{ total() | currency }} </td>
</tr>
</table>
<a href="#/checkout" class="btn btn-danger">
Checkout <span class="glyphicon glyphicon-shopping-cart" aria-hidden="true"></span>
</a>
</div>
When there are only 2 items and I refresh, the screen shows,
Name Qty Cost Total
Black Forest 1 1 1 [X]
Berry 1 2 2 [X]
Total: $3.00
Checkout
otherwise, it shows
Name Qty Cost Total
Total: $7.00
As the total is 7.. The data is still there but not getting displayed.

You stringify the entire invoice object, but when you read it back from local storage, you parse it into an object with a different structure.
Try $scope.invoice = JSON.parse($scope.saved) instead.

I solved my question. It was because of the duplicate keys in the array. Duplicate keys are banned because AngularJS uses keys to associate DOM nodes with items. So adding this did the trick.
ng-repeat="item in invoice track by $index"
So stupid of me!

Related

ng-repeat or something similar on two arrays

I recently started learning angular js. I have following object ResultRow:
ResultRow = function ( cars, prices) { //cars and prices are arrays
this.prices = prices;
this.cars = cars;
};
for example,
ResultsRow=new ResultsRow(["Ford","Honda","Nissan"],[20,22,18]);
I want to display the ResultsRow in table as:
Car Price
Ford 20
Honda 22
Nissan 18
So basically print car[0],price[0] in one table row, car[1], price[1] in next table row and so on.
I tried following using ng-repeat:
<tr>
<td ng-repeat="car in ctrl.ResultsRow.cars">
{{car}}
</td>
<td ng-repeat="car in ctrl.ResultsRow.prices">
{{price}}
</td>
</tr>
I am having a hard time figuring out how to display the exact format that I want. Any help will be appreciated.
P.S. It is quite complicated for me to change the format of ResultRow as I am generating it after quite a few data operations.
You should make an array of objects with properties to iterate in stead of trying to simultaneously iterate two separate arrays.
Example:
var row1 = {
car: "Ford",
price: 20
};
var row2 = {
car: "Honda",
price: 22
};
$scope.rows = [row1, row2];
Then in the template:
<tr ng-repeat="row in rows">
<td>{{row.car}}</td>
<td>{{row.price}}</td>
</tr>
if you want to use a multi-dimension array then I would suggest use new Array and if your car array and price array content is in order then you could do something like this;
vm.ResultsRow=new Array(["Ford","Honda","Nissan"],[20,22,18]);
<tr ng-repeat="car in vm.ResultsRow[0]">
<td>
{{car}}
</td>
<td>
{{vm.ResultsRow[1][$index]}}
</td>
</tr>
angular.module("app",[])
.controller("ctrl",function($scope){
var vm = this;
vm.ResultsRow=new Array(["Ford","Honda","Nissan"],[20,22,18]);
console.log(vm.ResultsRow)
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl as vm">
<table>
<tr ng-repeat="car in vm.ResultsRow[0]">
<td>
{{car}}
</td>
<td>
{{vm.ResultsRow[1][$index]}}
</td>
</tr>
</table>
</div>
change the function for mapping data, like this :
var repeatObject = new Array();
ResultRow = function ( cars, prices) { //cars and prices are arrays
if(cars.length == prices.length) {
for(let i in cars){
this.repeatObject.push({'cars' : cars[i], 'prices' : prices[i]});
}
}
};
So you can use 'ng-repeat' :
<tr ng-repeat="row in repeatObject">
<td>{{row.car}}</td>
<td>{{row.price}}</td>
</tr>

AngularJS orderBy a column containing a list

is it possible to use orderBy with something other than strings and numbers?
I have a list of customers and one of the columns contains an array of values (list of urls). I want to order by the url list, i.e. the first url in the list.
I thought the array would maybe be automatically converted to a string. I also tried using providerUrls[0] as orderBy criteria.
I know I could write my own sorting function but if there is a simple solution for this I would like to learn how to use it.
This is a reduced example of my code:
<table>
<thead>
<tr>
<td>
<p data-ng-click="sortBy = providerUrls; sortReverse = !sortReverse;">
URLs
<span data-ng-show="sortBy === providerUrls && !sortReverse">
<img src="arrow-down.svg"/>
</span>
<span data-ng-show="sortBy === providerUrls && sortReverse">
<img src="arrow-up.svg"/>
</span>
</p>
</td>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="provider in providerFilter = (providers | orderBy:sortBy:sortReverse) track by $index">
<td>
<p data-ng-repeat="url in provider.urls track by $index">
{{url}}
</p>
</td>
</tr>
</tbody>
</table>
My data structure:
var a = {
providerId: "a",
providerUrls: ["abc", "def", "ghi"]
// more properties
};
// b and c similar
$scope.providers = [a, b, c];

Populate and update a table with data from a different table

My site allows for a user to search for a term which returns a table of associated songs. When the "Add Track" button in a particular row is clicked after the search, the respective track name and trackId are added to the table "playlist". The problem I am having is that once "Add Track" is clicked within a different row, the data from that row is not added to the "playlist" table, but rather it just replaces the previous information. I need to be able to generate a cumulative table. Any help would be great and thanks in advance!
<body ng-app>
<body ng-app>
<div ng-controller="iTunesController">
{{ error }}
<form name="search" ng-submit="searchiTunes(artist)">
<input type="search" required placeholder="Artist or Song" ng-model="artist"/>
<input type="submit" value="Search"/>
</form>
<div class="element"></div>
<table id="SongInfo" ng-show="songs">
<thead>
<tr>
<th>Album Artwork</th>
<th>Track</th>
<th></th>
<th>Track Id</th>
<th>Preview</th>
<th>Track Info</th>
<th>Track Price</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="song in songs">
<td><img ng-src="{{song.artworkUrl60}}"
alt="{{song.collectionName}}"/>
</td>
<td>{{song.trackName}}</td>
<td><button ng-click="handleAdd(song)">Add Track</button></td>
<td>{{song.trackId}}</td>
<td>Play</td>
<td>View Track Info</td>
<td>{{song.trackPrice}}</td>
</tr>
</tbody>
</table>
<table id="playlist">
<thead>
<tr>
<th>Playlist</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="song in addedtracks">
<td>{{song.trackName}}</td>
<td>{{song.trackId}}</td>
</tr>
</tbody>
</table>
</div>
</body>
itunes_controller.js
var iTunesController = function($scope, $http){
$scope.searchiTunes = function(artist){
$http.jsonp('http://itunes.apple.com/search', {
params: {
'callback': 'JSON_CALLBACK',
'term': artist,
limit: 5,
}
}).then(onSearchComplete, onError)
}
$scope.handleAdd = function(song) {
// this song object has all the data you need
console.log("handle add ", song)
$scope.addedtracks = [{song:'trackName', song:'trackID'}]
$scope.addedtracks.push(song)
}
var onSearchComplete = function(response){
$scope.data = response.data
$scope.songs = response.data.results
}
var onError = function(reason){
$scope.error = reason
}
}
I saw some issues with your code. First the code below
$scope.addedtracks = [{song:'trackName', song:'trackID'}]
$scope.addedtracks.push(song)
Acording to your html, you are passing the song object to the handleAdd. So just remove the first line from code above. After that step, declare addedtracks array before handleAdd like below
$scope.addedtracks = [];
Modify the ng-repeat for the playlist like below:
<tr ng-repeat="song in addedtracks track by $index">
<td>{{song.trackName}}</td>
<td>{{song.trackId}}</td>
</tr>
And that's it. Note that I used track by $index because ngRepeat does not allow duplicate items in arrays. For more information read Tracking and Duplicates section.
Finally this is working plunker

AngularJS - ng-repeat not working with a map-entry named length and value 0

I would like to show Events with Subevents I got from an API as JSON
[
{
"class":"de.ff.prg.EventItem",
"id":27667,
"additional_info":null,
"comments":null,
"event":{"class":"Event","id":27657},
"length":0,
"runningorder":0,
"screening":{"class":"Screening","id":27529},
"title_eng":"'71",
"title_ger":"'71",
"venue":{"class":"Venue","id":1}},
{"class":"de.ff.prg.EventItem",
"id":27676,
"additional_info":null,
"comments":null,
"event":{"class":"Event","id":27657},
"length":5,
"runningorder":0,
"screening":null,
"title_eng":"NEW",
"title_ger":"NEW",
"venue":{"class":"Venue","id":8}
}
]
In order to display the fields of the items in rows and not in columns, I have nested two tables with ng-repeat so that I get a table of tables.
<!--Items-->
<table>
<thead>
<td colspan="6" style="background-color: #b9da73">
<button class="btnAdd" ng-click="addAndEditEventItem()">Add Item</button>
</td>
</thead>
<tbody>
<tr ng-repeat="item in eventItems">
<h1>{{eventItems.length}}</h1>
<th>
<table>
<thead>
<td colspan="2" style="background-color: #c0da86">{{item.runningorder}}</td>
<td colspan="4" style="background-color: #c0da86">
<button class="btnAdd" ng-click="deleteEventItem(item.id)">Delete Item</button>
</td>
</thead>
<tbody>
{{item}}
<tr ng-repeat="(key,value) in item">
<th colspan="2" style="background-color: #ceeca1">{{key}}</th>
<th colspan="4" style="font-weight: normal;">{{value}} </th>
</tr>
</tbody>
</table>
</th>
</tr>
</tbody>
</table>
Up until now this was no problem, but somewhere along the way I have lost the possibility to display the body of the first sub-table (all other rows render fine). I have inserted {{item}} before the body tag and it shows the missing data, so it's there all right.
Any ideas? Or do you need to see the other code to tell? I have no clue...
Here is a Fiddle
Interesting case.
Empirically, I found that this is because of
"length":0
Just looked into angular source code and found that it transforms object's properties in repeat to an array, and then takes it's length property to iterate through it.
...ngRepeatDirective...
if (isArrayLike(collection)) {
collectionKeys = collection;
trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
} else {
trackByIdFn = trackByIdExpFn || trackByIdObjFn;
// if object, extract keys, sort them and use to determine order of iteration over obj props
collectionKeys = [];
for (key in collection) {
if (collection.hasOwnProperty(key) && key.charAt(0) != '$') {
collectionKeys.push(key);
}
}
collectionKeys.sort();
}
arrayLength = collectionKeys.length; <<<--- HERE
// locate existing items
length = nextBlockOrder.length = collectionKeys.length; <<<--- AND HERE
So nothing really happens.
Seems like a bug actually. I've posted an issue.
Just try then iterating through your array and change name of this property. Like:
for(var i = 0; i < $scope.eventItems.length; i++){
$scope.eventItems[i].itemLength = $scope.eventItems[i].length;
delete $scope.eventItems[i].length;
}

knockout js showing related objects(selected id) on click

I'm new to Knockout js and need some advice. What I am trying to do (the correct way) is have orders listed in a grid and a "production" button that when it is click, will show only the production objects that have matching id's to the order id. I'm trying to wrap my head around Knockouts binding, but I think I am over thinking things.
right now I have 2 objects Order and Production with are observable arrays filled with observables. Order has value of orderId and Production have value of prodId that I am checking for a match. I'm now wondering if I should not make this on object with mutli-dimensional array. Would it be easier to show selected data that way?
here is an example of the initial arrays
var initProduction = [
new Production({
proId:"183175",
pType:"Art TIme",
startTime:"11:20",
stopTime:"11:50",
totalTime:"",
by :"MJ"
})
var initData = [
new Order({
date:"06-09-2014",
orderId:"183175",
name:"Columbus Africentric",
dateRec:"05-23-2014",
rushDate:"",
totalQty:55,
parts:"1",
auto:"No",
type:"Local",
})
]
so should I combine into a multidimensional array? And if so, how would I do that? And how would I create a click event to show related data in another table showing only the production info.
I hope this makes sense and someone can help me. I apologize for my ignorance.
here is a stripped down version of my html bindings
<table>
<tbody data-bind="foreach:filteredOrders">
<tr>
<td>
<label class="read" data-bind="text:orderId, visible:true" />
</td>
<!-- controls -->
<td class="tools">
<button class="button toolButton" data-bind="click: $root.showSummary">Show Production</button>
</td>
</tr>
</tbody>
</table>
<h3>Production Summary</h3>
<table class="ko-grid" id="menu" >
<tbody data-bind="foreach:filteredProds">
<tr>
<td>
<div>
<label class="read" data-bind="text:proId, visible:true" />
</div>
</td>
</tr>
</tbody>
</table>
I would just have an orders array and then link the production object to the order.
var model = {
orders: [
{
date:"06-09-2014",
orderId:"183175",
name:"Columbus Africentric",
dateRec:"05-23-2014",
rushDate:"",
totalQty:55,
parts:"1",
auto:"No",
type:"Local",
production: {
proId:"183175",
pType:"Art TIme",
startTime:"11:20",
stopTime:"11:50",
totalTime:"",
by :"MJ"
}
},
{
date:"06-09-2014",
orderId:"183176",
name:"Angle Africentric",
dateRec:"05-23-2014",
rushDate:"",
totalQty:55,
parts:"1",
auto:"No",
type:"Local"
}
]
};
In the above json the second order doesn't have a production object.
Then in the viewModel I would use a computed which will return the orders depending on if all orders or only production orders should be shown. I've created a toggle here which is linked to the button.
var ViewModel = function (model) {
var self = this;
self.orders = $.map(model.orders, function (order) { return new Order (order); });
self.toggleProductionMode = function (order) {
order.showProductionOrder(!order.showProductionOrder());
};
};
var Order = function (order) {
var self = this;
ko.utils.extend(self, order);
self.showProductionOrder = ko.observable(false);
};
View:
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
</thead>
<tbody data-bind="foreach: orders">
<tr>
<td data-bind="text: orderId"></td>
<td data-bind="text: name"></td>
<td data-bind="if: production"><button data-bind="click: $root.toggleProductionMode">Toggle Production Orders</button>
</td>
</tr>
<tr data-bind="visible: showProductionOrder, with: production">
<td colspan="3">
<table>
<tr>
<th>proId</th>
<th>pType</th>
</tr>
<tr>
<td data-bind="text:proId"></td>
<td data-bind="text:pType"></td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
Demo here: http://jsfiddle.net/X3LR6/2/

Resources