I am setting the default orderby on a directive. On certain click events I'm changing the order by the column that was clicked upon. However when I try to set a default order by the index I get some really strange results.
I've created a Plunker based off the Angular Documentation. My goal is to have the default orderBy (only code shown here) perform no sorting so that I can change the orderBy property later.
Plunker: Plunker Or HTML Below
<div ng-controller="ExampleController">
<table class="friend">
<tr>
<th>Name</th>
<th>Phone Number</th>
<th>index</th>
</tr>
<tr ng-repeat="friend in friends | orderBy:'index'">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<td>{{friends.indexOf(friend)}}</td>
</tr>
</table>
</div>
New Plunker. It appears as if this is a change from Angular 1.2 to 1.3 I tested working code on different versions of angular and in 1.2 you can pass a empty order by and it will order by the index but in 1.3 and 1.4 it sorts differently
You don't need any sorting at all here, the array is already in the order you want it to be. Assuming that you have a variable $scope.orderByField defining which property you want to use for ordering you could do something like:
<tr ng-repeat="friend in (orderByField ? (friends | orderBy : orderByField) : friends)">
So you would use the orderBy filter only if you really need it.
Plunker: http://plnkr.co/edit/kXOSF4adOohDDxLEtnGT?p=preview
Appart from that you can also define a custom comparator method and use it:
$scope.sortByIndex = function(a, b) {
return $scope.friends.indexOf(a) - $scope.friends.indexOf(b)
}
<tr ng-repeat="friend in friends | orderBy : sortByIndex">
But i wouldn't really recommend to use that, cause you will do unnecessary calculations.
I solved this issue by presorting my list at all times.
Then my function did all the work I was previously relying on Angular to do for me.
As an FYI I also submitted this as a bug to the angularJS team. They reported that it will be fixed in the next release (https://github.com/angular/angular.js/issues/12100)
Related
I'm trying to integrate NgTable with angularjs 1.5 in a component-based architecture (so, no old-style controllers).
The NgTable controls (filter, icons for sorting) appear, but they don't seem to work at all, so they don't filter and don't sort.
Here's my code
--- activity-list-module.js ---
angular.module('activityList', ['ngTable']);
--- activity-list-component.js ---
(function(){
'use strict';
angular.
module('activityList').
component('activityList', {
templateUrl: "app/activity-list/activity-list.html",
controller: ['NgTableParams', function (NgTableParams) {
var self=this;
self.activitiesForm=[{id:0,description:"att0"},{id:1,description:"att1"},{id:2,description:"att2"}];
self.tableParams = new NgTableParams({}, { dataset: self.activitiesForm});
}]
});
})();
--- activity.list.html ----
<div style="margin:20pt">
<h3>Manage Activities</h3>
<div style="margin:20pt">
<h3>Manage Activities</h3>
<div class="row" style="margin-left:20pt" >
<table id="myViewTable" ng-table="$ctrl.tableParams" class="table" show-filter="true" class="table actTable" style="width:95%;table-layout: fixed;" >
<tbody>
<tr ng-repeat="projectActivity in $ctrl.activitiesForm track by projectActivity.id">
<td data-title="'Id'">{{projectActivity.id}}</td>
<td data-title="'description'" filter="{ description: 'text'}" sortable="'description'">{{projectActivity.description}}</td>
</tr>
</tbody>
</table>
</div>
</div>
Of course the component is called using the tag <activity-list></activity-list> in another page.
I'm working with AngularJs 1.5.8 and ng-table 3.0.1.
I'm doing something wrong or there is an incompatibility between AngularJs >= 1.5 and ng-table ?
Here is the plnkr
PS In the last case, how can I do ? Is smarttable compatible with AngularJs 1.5 ?
After raised an issue in ng-table GIT repo i got a working example from the creator of ng-table and i could have a look.
The point is in the use of controller variables, instead of a $data variable generated by ng-table.
So, in the case of the 'new style' component the issue is solved changing
<tr ng-repeat="projectActivity in $ctrl.activitiesForm track by projectActivity.id">
with
<tr ng-repeat="projectActivity in $data track by projectActivity.id">
In the case of the "old Style" controller it's solved changing
<tr ng-repeat="user in vm.data">
with
<tr ng-repeat="user in $data">
I don't like a lot this solution, because it add a kind of global, but it works.
EDIT
it works also using
$ctrl.tableParams.data (for Component Example) and vm.tableParams.data (for the Controller Example) which let me feel much better about.
I have an ng-repeat table with a handful of columns. One of te columns is an image field. Here is a snippet with the image column and one of the others:
<tbody>
<tr ng-repeat="insight in insights | orderBy:sort track by $index">
<td>
<select ng-model="insight.type" ng-change="setType(insight)" required="required">
<option value="INSIGHT">Insight</option>
<option value="EVIDENCE">Evidence</option>
</select>
</td>
<!--Image thing-->
<td>
<a ng-href="{{:: insight.imgurl}}" target="_blank"><img class="full" wb-lazy-load-image="{{:: insight.imgurl}}" data-height="600" data-width="800"></a>
</td>
...
</tbody>
As it stands, when I click to sort by a column, all of the columns sort with their associated row, except for the image row; it stays in the same spot.
If I remove the track by $index code at the <tr> it reorders all the columns as expected.
Any idea why or what I can do to fix that?
Edit:
FWIW - sort is defined in my Controller as: $scope.sort = ['-primary', '-live'];
Edit (Solution):
I ended up removing the one time binding as suggested in the accepted answer and it fixed the issue. However, after some consideration, we decided we'd rather have the performance gain of our one time, lazy loads than the track by performance as the lists would be small.
For anyone else who has this issue, one time binding with the images in conjuction with track by $index seemed to be the issue in this case.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="orderByExample">
<script>
angular.module('orderByExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.friends =
[{name:'John', phone:'555-1212', age:10,sort:['-age','-name']},
{name:'Mary', phone:'555-9876', age:19,sort:['-age','-name']},
{name:'Mike', phone:'555-4321', age:21,sort:['-age','-name']},
{name:'Adam', phone:'555-5678', age:35,sort:['-age','-name']},
{name:'Julie', phone:'555-8765', age:29,sort:['-age','-name']}];
}]);
</script>
<div ng-controller="ExampleController">
<table class="friend">
<tr>
<th>Name</th>
<th>Phone Number</th>
<th>Age</th>
</tr>
<tr ng-repeat="friend in friends | orderBy:sort track by $index">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<td>{{friend.age}}</td>
</tr>
</table>
</div>
</body>
According to the AngularJS Doc:
Note: track by must always be the last expression:
https://docs.angularjs.org/api/ng/directive/ngRepeat
An example which might help you:
<div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id">
{{model.name}}
</div>
It appears as though the one time binding syntax is the culprit. using {{::insight.imgurl}} would create a one time binding, where the watcher is removed from this element. However, when used in combination with wb-lazy-load-image, it would appear that the directive believes it is getting a two way binding but is in fact getting a one way binding.
My best guess without seeing how this directive works is that the directive takes the insight.imgurl and performs a network call to find the image. Then the filter causes the array to change order, but the directive isn't aware that the order has changed, due to the one way binding. Therefore, the directive loads the image in the incorrect place. Removing track by $index would cause the ng-repeat to be handled differently in the $digest loop, causing the problem directive to be completely re-rendered.
Removing :: from the binding to the directive input should allow the directive to function as intended, even without a full re-render. In general, :: is only intended for use in cases where the expressions will be permanent after rendering; in this case, they are not permanent due to the order filter.
I am new to Angular JS and I am trying to use ng-repeat to generate multiple row in my table, and in each row there should be a button to trigger a function with different parameter
<div ng-controller="testing">
<table>
<thead>
<tr>
<th>col 1</th>
<th>col 2</th>
<th>button</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in items">
<td>{{ item.prop1 }}</td>
<td>{{ item.prop2 }}</td>
<td><button ng-click="myFunction(item.id)">Trigger Function</button></td>
</tr>
</tody>
</table>
</div>
This of course doesn't work, as the ng-repeat creates its own scope, while myFunction is defined in the parent scope (i.e. the testing controller).
However, all I can find in Google result is the reason of not working, but lacks a way to fix the problem. How should I correct my code so that the ng-click works? Is there a way to define myFunction inside ng-repeat scope or is there a way to tell I want the function in the parent scope?
Change the ng-click to following
<button ng-click="testing.myFunction(item.id)">
Improve ng-repeat performance – using track by with ng-repeat
Pass $index to track your ng-repeat and which ng-click is being cliked
<tr ng-repeat="item in items track by $index">
<td>{{ item.prop1 }}</td>
<td>{{ item.prop2 }}</td>
<td><button ng-click="myFunction($index,item.id)">Trigger Function</button></td>
</tr>
For Reference
Probably one of the very first things you learned doing with Angular was how to use ngRepeat. It’s so easy and simple, you gotta love it. Here’s an example:
<ul class="tasks">
<li ng-repeat="task in tasks" ng-class="{done: task.done}">
{{task.id}}: {{task.title}}
</li>
</ul>
The problem
Now say you have a button above that list to refresh it.
The obvious implementation for this refresh would be something along these lines in your controller:
$scope.tasks = newTasksFromTheServer;
This is a trivial piece of code, but it would cause ngRepeat to remove all li elements of existing tasks and create them again, which might be expensive (e.g. we have a lot of them or each li’s template is complex). That means a lot of DOM operations.
Why would Angular do this? Behind the scenes ngRepeat adds a $$hashKey property to each task to keep track of it. If you replace the original tasks with new tasks objects from the server, even if those are in fact totally identical to your original tasks, they won’t have the $$hashKey property and so ngRepeat won’t know they represent the same elements.
track by to the rescue
In Angular 1.2 a new addition was made to the syntax of ngRepeat: the amazingly awesome track by clause. It allows you to specify your own key for ngRepeat to identify objects by, instead of just generating unique IDs.
This means that you can change the above to be ng-repeat="task in tasks track by task.id" and since the ID would be the same in both your original tasks and the updated ones from the server – ngRepeat will know not to recreate the DOM elements and reuse them. Boom.
you can also use ‘track by $index’ if your data source has duplicate data
//Below code will throw Error: dupes Duplicate Key in Repeater
<div ng-repeat="value in [4, 4]"></div>
//Below code will work perfectly fine
<div ng-repeat="value in [4, 4] track by $index"></div>
I can do this in Angular.js:
<tr ng-repeat="cust in customers">
<td>
{{ cust.name }}
</td>
</tr>
Which would iterate through all the customers by putting each in a <tr> of its own.
But what if I wanted two customers in one <tr>? How would I accomplish that?
I normally do that by messing around with indexes and modulus values, but I'm not sure how to do that here.
It turns out this can be done without any custom filters or changing the format of your data, though it does require some extra markup. I thought this woudn't be possible at first as you can't use a span or anything similar within the table for your ng-repeat. This answer however, points out that you can use a tbody.
So it's just a case of using the ng-if directive (which renders html if the expression is true), the $index variable (provided by ng-repeat) and the $even variable (which is also provided by ng-repeat and is true when $index is even
I've created a demo in this Plunker
<div ng-controller="MainCtrl">
<table>
<tbody ng-repeat="cust in customers">
<tr ng-if="$even">
<td>{{customers[$index].text}}</td>
<td>{{customers[$index+1].text}}</td>
</tr>
</tbody>
</table>
</div>
This would of course only work if you have two columns, what if you have more? Well you can also put a full expression into ng-if rather than just a variable. So you can use modulus values like this:
<tbody ng-repeat="cust in customers">
<tr ng-if="($index % 3) == 0">
<td>{{customers[$index].text}}</td>
<td>{{customers[$index+1].text}}</td>
<td>{{customers[$index+2].text}}</td>
</tr>
</tbody>
I wanted to have the functionality of rearranging rows in a table (sorting rows using drag and drop).
And the index of the row arrangement should also change in the model.
How can I do something similar to this : http://jsfiddle.net/tzYbU/1162/
using Angular Directive?
I am generating table as :
<table id="sort" class="table table-striped table-bordered">
<thead>
<tr>
<th class="header-color-green"></th>
<th ng-repeat="titles in Rules.Titles">{{titles.title}}</th>
</tr>
</thead>
<tbody ng-repeat="rule in Rules.data">
<tr>
<td class="center"><span>{{rule.ruleSeq}}</span></td>
<td ng-repeat="data in rule.ruleData">{{statusArr[data.value]}}</td>
</tr>
</tbody>
</table>
I did it. See my code below.
HTML
<div ng:controller="controller">
<table style="width:auto;" class="table table-bordered">
<thead>
<tr>
<th>Index</th>
<th>Count</th>
</tr>
</thead>
<tbody ui:sortable ng:model="list">
<tr ng:repeat="item in list" class="item" style="cursor: move;">
<td>{{$index}}</td>
<td>{{item}}</td>
</tr>
</tbody>{{list}}
<hr>
</div>
Directive (JS)
var myapp = angular.module('myapp', ['ui']);
myapp.controller('controller', function ($scope) {
$scope.list = ["one", "two", "thre", "four", "five", "six"];
});
angular.bootstrap(document, ['myapp']);
There is another library: RubaXa/Sortable: https://github.com/RubaXa/Sortable
It is for modern browsers and without jQuery dependency. Included is a angular directive. I'm going to check it out now.
You get good touch support additionally.
AngularJS was not really built for the manipulation of DOM elements, rather to extend the HTML of a page.
See this question and this Wikipedia entry.
For DOM manipulation, jQuery/mootools/etc will suite you just fine (hint: the example in your jsFiddle link).
You could probably use AngularJS to keep track of the ordering of your elements to update your model. I'm not sure how to do this using directives, but the following code may be useful
var MyController = function($scope, $http) {
$scope.rules = [...];
...
}
var updateRules = function(rule, position) {
//We need the scope
var scope = angular.element($(/*controller_element*/)).scope(); //controller_element would be the element with ng-controller='MyController'
//Update scope.rules
}
Then when you reorder the list, simply call updateRules() with the changed rule and its new position in the model.
Anyone else who wants something like this but not understanding the accepted answer. Here is a directive UI.Sortable for AngularJS that allows you to sort an array/ table rows with drag & drop.
Requirements
JQuery v3.1+ (for jQuery v1.x & v2.x use v0.14.x versions)
JQueryUI v1.12+
AngularJS v1.2+
Usage
Load the script file: sortable.js in your application: (you can find this sortable.js from here
<script type="text/javascript" src="modules/directives/sortable/src/sortable.js"></script>
make sure you have included JQuery, AngularJs and JQueryUI js files in
order before this sortable file
Add the sortable module as a dependency to your application module:
var myAppModule = angular.module('MyApp', ['ui.sortable'])
Apply the directive to your form elements:
<ul ui-sortable ng-model="items">
<li ng-repeat="item in items">{{ item }}</li>
</ul>
Developing Notes:
ng-model is required, so that the directive knows which model to update.
ui-sortable element should contain only one ng-repeat
Filters that manipulate the model (like filter, orderBy, limitTo,...) should be applied in the controller instead of the ng-repeat
3rd point is very Important as it took almost an hour to understand
why my sorting was not working?? It was because of orderBy in html
and that was resetting the sorting again.
For more understanding you can check the detail here.
If I understand you correctly, you want to be able to sort the rows? If so, use UI-Sortable: GitHub
Demo: codepen.io/thgreasi/pen/jlkhr
Along with RubaXa/Sortable there is one more angularjs library avilable that is angular-ui-tree. Along with drag and drop we can arrange elements in a tree structure and we can add and delete elements (nodes)
Please see the this link for examples
http://angular-ui-tree.github.io/angular-ui-tree/#/basic-example .
Please see this for github
https://github.com/angular-ui-tree/angular-ui-tree.