How to bind object to a table in AngularJS? - angularjs

I'm trying to databind an object received via an HTTP request to a table in angularjs.
The normal way to do this would be to use ng-repeat as follows.
<table class="table table-striped">
<tr>
<th>name</th>
<th>artist</th>
</tr>
<tr>
<td ng-repeat="track in $scope.trackList.items">
{{ track.name }}
{{ track.artist }}
</td>
</tr>
</table>
The problem with this is that the page loads and ng-repeat is ran before the data is returned from the server causing no items to be in the collection so nothing is drawn into the table.
What would be the best way to do this?

You don't need $scope in your view. Also, you probably want to put ng-repeat on <tr>, not <td>.
<table class="table table-striped">
<tr>
<th>name</th>
<th>artist</th>
</tr>
<tr ng-repeat="track in trackList.items">
<td>{{ track.name }}</td>
<td>{{ track.artist }}</td>
</tr>
</table>

You don't need $scope here just simple trackList.items.
$scope is the glue b/w Views and controller and you don't need to explicitly call $scope in the view it is already implicit that things are already picking from the scope.
<td ng-repeat="track in trackList.items">
{{ track.name }}
{{ track.artist }}
</td>
And if you are talking about the http call then here come's the magic of two way binding into picture.If anything update in controller by $http call that will be reflect in view as well you don't need to do it manually.
Hope it helps. :)

Let me correct your code first
<table class="table table-striped">
<tr>
<th>name</th>
<th>artist</th>
</tr>
<tr>
<td ng-repeat="track in trackList.items">{{ track.name }}</td>
<td>{{ track.artist }}</td>
</tr>
</table>
You don't have to explicitly specify $scope while binding data.
With respect to your problem, even though the code will get executed when the page is getting loaded, any changes that you do to the scoped data will be honored by the angular and view will get updated.
In your case, at the time of page load, if there is data available in trackList.items it will be shown in the page. Otherwise table will be rendered with just headers. Later when the application receives data from AJAX (or any other source), you have to simply assign it to $scope.trackList in your JS code. This will result in instant view updates and you will see the table starts reflecting the new data.

Related

Nested ng-repeat failing on inner loop

I am having trouble trying to present data from an api. The data comes in a json response organized like this API from Guild Wars for character data
I made an angularJS request and used a series of nested ng-repeats to present the data, but the innermost loop , the inventory data , isn't being present properly. Only some of it is being iterated.
This is my code :
<table>
<tr ng-repeat="char in $ctrl.chars | orderBy:'-age'">
<td>
<table id="charTotal">
<ng-include src="'charBasic.html'"></ng-include>
<ng-include src="'charBags.html'"></ng-include>
</table>
</td>
</tr>
</table>
Forgot to include charBasic.html :
<tr>
<td>
<table id="charBasic" class="charBasic">
<tr>
<td>{{char.name}}</td>
<td>{{char.race}}</td>
<td>{{char.profession}}</td>
<td>{{char.level}}</td>
<td>{{char.deaths}}</td>
<td>
<table>
<tr ng-repeat="craft in char.crafting">
<td>{{craft.discipline}}</td>
<td>{{craft.rating}}</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
charBags.html :
<tr>
<td>
<table id="charBags" class="charBags" border="1">
<tr ng-repeat="bag in char.bags">
<td>{{bag.id}}</td>
<td>{{bag.size}}</td>
<td ng-repeat="inv in bag.inventory"><ng-if "{{inv}}">{{inv.id}}</ng-if></td>
</tr>
</table>
</td>
</tr>
As it is, not all inventory arrays are iterated, and the results vary each time
What would be the correct way to iterate this data ?
I found out the problem, it was a duplicate item issue :(
I "solved" it by using track-by $index option inside ng-repeat, which will bypass the problem of duplicate ids.

AngularJs redirect

I've got a table where 1 row has to link to something else.
Right now I've got it like this:
<table class="table table-hover">
<thead>
<tr>
<th>Functie</th>
<th>Bedrijf</th>
<th>Naam</th>
<th>Achternaam</th>
</tr>
</thead>
<tbody ng-show="!homeCtrl.loading">
<tr ng-repeat="employee in homeCtrl.employees" ng-click="homeCtrl.redirectToShow(employee.EmployeeId)">
<td>{{ employee.Role }}</td>
<td>{{ employee.Company }}</td>
<td>{{ employee.FirstName }}</td>
<td>{{ employee.LastName }}</td>
<td ng-show="homeCtrl.canEdit" ng-click="homeCtrl.redirectToEdit(employee.EmployeeId)"><span class="glyphicon glyphicon-pencil"></span></td>
</tr>
</tbody>
</table>
The last td has to link to something else. But right now all links to the same how could I manage that?
(In the ng-click method I redirect)
There are two problems in your current implementation.
You could use $last to bind click event to fire on last element. I had put $last before homeCtrl.redirectToEdit function call because that will make sure until $last(last element of ng-repeat appears) row gets clicked then only it will call the next function.
ng-click="$last && homeCtrl.redirectToEdit(employee.EmployeeId);$event.stopPropagation()"
I think you are facing problem related to event bubbling, where you have click event binded to child and parent element. So when click event happen inside inner element it will propagate that event to its parent. To avoid such unwanted behaviour you should stop inner event bubbling just by calling $event.stopPropagation() on inner td.
You can use $last(boolean) and pass as parameter to your ng-click function, then redirect the user depending if is the last element or not.
<td ng-show="homeCtrl.canEdit" ng-click="homeCtrl.redirectToEdit(employee.EmployeeId, $last)"><span class="glyphicon glyphicon-pencil"></span></td>
try this
<td ng-show="$last && homeCtrl.canEdit" ng-click="homeCtrl.redirectToEdit(employee.EmployeeId)"><span class="glyphicon glyphicon-pencil"></span></td>
You can use $last and a condition:
<td ng-show="homeCtrl.canEdit && !$last" ng-click="homeCtrl.redirectToEdit(employee.EmployeeId)"><span class="glyphicon glyphicon-pencil"></span></td>
<td ng-show="homeCtrl.canEdit && $last" ng-click="homeCtrl.somethingDifferent(employee.EmployeeId)"><span class="glyphicon glyphicon-pencil"></span></td>
Ideally, You should set the ng-click to be per td because setting an ng-click to the full row will take precedence over any ng-clicks inside it. Try this:
<table class="table table-hover">
<thead>
<tr>
<th>Functie</th>
<th>Bedrijf</th>
<th>Naam</th>
<th>Achternaam</th>
</tr>
</thead>
<tbody ng-show="!homeCtrl.loading">
<tr ng-repeat="employee in homeCtrl.employees">
<td ng-click="homeCtrl.redirectToShow(employee.EmployeeId)">{{ employee.Role }}</td>
<td ng-click="homeCtrl.redirectToShow(employee.EmployeeId)">{{ employee.Company }}</td>
<td ng-click="homeCtrl.redirectToShow(employee.EmployeeId)">{{ employee.FirstName }}</td>
<td ng-click="homeCtrl.redirectToShow(employee.EmployeeId)">{{ employee.LastName }}</td>
<td ng-show="homeCtrl.canEdit" ng-click="homeCtrl.redirectToEdit(employee.EmployeeId)"><span class="glyphicon glyphicon-pencil"></span></td>
</tr>
</tbody>
That said, if you want to keep your markup the way it is, you can look at this StackOverflow answer about stopping bubbling: Howto: div with onclick inside another div with onclick javascript

Save predicate orderBy on page refresh

Is it possible to save the orderBy so that if a user sorts the data then refreshes the page, the last sort (column and ascending/descending) is still apparent?
Here is my view.
<table>
<tr class="sortheaders">
<th class="small"> </th>
<th class="medium">Name</th>
<th class="medium">Age</th>
</tr>
<tr ng-repeat="result in results | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{ result.name }}</td>
<td>{{ result.age }}</td>
</tr>
</table>
The simplest way to save data over a refresh would be by saving cookies in the user's browser. You can inject an ngCookies module. Read more about cookies and how to use the module here.

ng-repeat in combination with ng-init

Controllers loads $scope.shops on init already. Async with defer and promise etc.. I create a panel for each shop there is available. Then I'd like to add columns for each item.
I have a method in controller getItemsPerShop(item) which is also async etc.. I currently get two panels (there are two shops), but the items are the same.. probably cause of the async issue and the $scope.items not getting downloaded fast enough, how would I go about solving this.
<div ng-repeat="shop in shops">
<div class="panel panel-default">
<div class="panel-heading">shop {{shop}}</div>
<table class="table">
<thead ng-init="getItemsPershop(shop)">
<tr>
<th>Number</th>
<th>Date</th>
</tr>
</thead>
<tbody ng-repeat="item in items">
<tr>
<th scope="row">{{item.nr}}</th>
<td>{{item.desc}}</td>
</tr>
</tbody>
</table>
</div>
</div>
It's like a nested situation where the each panel need to load it's rows.
I will go for making getItemsPershop() in controller.
Did you forget the items = in ng-init?
<div ng-repeat="shop in shops">
<div class="panel panel-default">
<div class="panel-heading">shop {{shop}}</div>
<table class="table">
<!-- forgot "items =" ? -->
<thead ng-init="items = getItemsPershop(shop)">
<tr>
<th>Number</th>
<th>Date</th>
</tr>
</thead>
<tbody ng-repeat="item in items">
<tr>
<th scope="row">{{item.nr}}</th>
<td>{{item.desc}}</td>
</tr>
</tbody>
</table>
</div>
</div>
The way your template looks, you have one array items in your scope. However, you need to nest it in the shop, otherwise you can't distinguish them.
In the end it will look like this:
<tbody ng-repeat="item in shop.items">
<tr>
<th scope="row">{{item.nr}}</th>
<td>{{item.desc}}</td>
</tr>
</tbody>
As pointed out in another answer you can assign the items to a field items in a scope local to the current shop. However I would discourage you from doing so. From the docs:
There are only a few appropriate uses of ngInit, such as for aliasing special properties of ngRepeat, as seen in the demo below; and for injecting data via server side scripting. Besides these few cases, you should use controllers rather than ngInit to initialize values on a scope.
In angular you are supposed to init a model in your controller and render it via a template. Mixing these two makes your code harder to understand, test, and maintain.

Adding a list of events in angular

I'm not sure how to do this in angular, after coming from jquery.
I have a table:
<div class="col-xs-10">
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
</thead>
<tbody ng-repeat="val in data">
<tr>
<td>val.Time</td>
<td>val.Distance</td>
<td ng-click="callmethod()"><img src="delete"></td>
</tr>
</tbody>
</table>
</div>
Essentially I want the callmethod() to know which row is being clicked so that I can make a update in the model in my controller. What is the right way to do this?
You can use the $index property:
callmethod($index)
Then on your controller you would do something like:
function callmethod(index) {
var foo = $scope.data[index];
}
Change that ng-click to this:
<td ng-click="callmethod(val)"><img src="delete"></td>
You'll get the whole val object when that method gets called.

Resources