angularjs bind function call to span without triggering event - angularjs

I use ng-repeat to populate my table. One of the columns in the table should be dynamically populated again by a different function call.
Below is my code snippet.
<tr ng-repeat="item in ctrl.items">
<td><span ng-bind="item.name"></span></td>
<td><span ng-bind="getItemDetails(item.id)"></span></td>
</tr>
I have array of items. I need to display those items in a table. Item name will be present in the item object, however, item details will be populated by another function call which needs item id.
On using ng-bind (like in the code above) I face 2 issues.
Multiple calls to function even if array has 1 items. Sometimes it goes on thereby freezing my browser and server out of memory issue
The item id doesn't get passed to function always. Sometimes it is undefined.
I am not sure if ng-bind is the right directive to be used. ng-model doesn't work though. Is there any other directive or other way to do it?
How can I achieve this?
EDIT:
Here is the jsfiddle url: https://jsfiddle.net/grubxaur/
If you check browser console, you can see the function is called twice. I guess it is called N no. of times where N is no. of columns in the table.

I have tweaked my implementation a bit to get rid of this issue. Rather than calling a function within ng-repeat, I modified the items array within the controller using angular.forEach before ng-repeat is invoked.
Something like code below.
angular.forEach(self.items, function(item){
item.details = $scope.getItemDetails(item.id);
});

Related

NG-Repeat doesn't update cleanly when scope is updated

So I have a list of workspaces:
<li class="workspace-object" ng-repeat="w in workspaces | filter:searchQuery" ng-click="selectWorkspace(w)">
{{w.name | titleCase }}
</li>
That List gets updated in the database by some function, and then I call the loadWorkspaces function below, to re-populate the $scope.workspaces object
$scope.loadWorkspaces = function() {
APIService.getWorkspaces().then(function(data){
$scope.workspaces = data;
});
}
When this happens, the $scope.workspaces, when logged out, reads the right updated information immediately, however, the ng-repeat DUPLICATES, before it updates to the proper list. I have no idea why this happens. Any ideas?
In this example, I am updating the workspace title, and that update runs the loadworkspaces function.
Try this ...
w in workspaces track by $index
When the contents of the collection change, ngRepeat makes the corresponding > changes to the DOM:
When an item is added, a new instance of the template is added to the DOM.
When an item is removed, its template instance is removed from the DOM.
When items are reordered, their respective templates are reordered in the DOM.
By default, ngRepeat does not allow duplicate items in arrays. This is because when there are duplicates, it is not possible to maintain a one-to-one mapping between collection items and DOM elements.
If you do need to repeat duplicate items, you can substitute the default tracking behavior with your own using the track by expression.
For example, you may track items by the index of each item in the collection, using the special scope property $index
Accepted answer did not help me (Using Angular 1.1.10), my repeat was still updating delayed. I struggled for ages before I got it working.
Instead of repeating my custom directive like so;
<my-directive ng-repeat="item in myarray track by item.id" some-binding="item"></my-directive>
I (magically) got it working after moving the ng-repeat to a (non-directive) parent element, like so:
<div ng-repeat="item in myarray track by item.id">
<my-directive some-binding="item"></my-directive>
</div>

ng-repeat with track by over multiple properties

I have a problem with angular ng-repeat directive.
Currently I work on some project where from the API I get a list of items (some times it could be 1k items) and this list should be refreshed every 5 seconds (it is monitoring related project).
When the list length is a little bigger the website while re-rendering DOM could "slow". It comes out that angular regenerate the whole DOM (but 95% of item are the same ! )
One of the possible approach is to set "track by" expression for example to item.id. But here comes another problem, I also want regenerate items when for example descriptions was changed by other user. Since track by is expression to item.id changes in item.description didn't update item in DOM.
There is way to track by over multiple properties? Maybe some function?
Or maybe do comparison by "hand" ?
Any ideas, code samples I would appreciate :)
UPDATE
what I discover when I set track by to item.id angular didn't re-crete html for items, just update value in already created element and it seems to be "faster" then removing and creating.
Previously I though a little bit different.
FIX
For those who are looking for better performance over >1k items in ng-repeat USE track by item.id it will boost your performance ;)
You do not need to create a function to handle track by multi properties.
You can do:
<div ng-repeat="item in lines track by item.id+item.description">
As the comment suggested you could try something like this:
<select ng-model="item" ng-options="item.id as item.description for item in items track by itemTracker(item)">
In your controller:
$scope.itemTracker= function(item) {
return item.id + '-' + item.description;
}
This might help with the number of DOM elements being re-rendered when the list changes.
Based my knowledge, the angularjs model is bind to the ui view, so the model will rerender via $apply or $digest once the value changed. so in your case, u wan bind the model value to ui view but also do not want to re-render the view if the value has not change,that is impossbile. this is what i know.
however, u can just manipulate the dom element. for example
store the data to a variable
var x = [{id:"id1",value:"v1"},{id:"id2",value:"v2"}]
in html, manual append or using directive to append, then assign the id to the element,
<div id="id1">v1</div>
check and compare the value, based ur needs.
once found, then angular.element("#yourid").text()
this will solve your browser resources consume problems.

AngularJS - original index of an object within filtered ng-repeat

I am using a nested ng-repeat and a filter on a object. The first ng-repeat is filters to the headerId in a gapHeader object. The second ng-repeat filters gapSection, sectionId to the corresponding headerID.
I have an edit page which is within a separate modal window. The purpose is to edit content corresponding to the headerID & sectionID of the sub-object) This also has a separate control. Data is shared through a service.
My problem I have a button for each gapSection sub-object, which opens the edit page modal, when I pass the $index value for the current section within each section to the service, I get the $index only corresponding to the second ng-repeat? For example, if I click the button within the 2 ng-repeat on gapSection (headerId:2, sectionId:2), I get an $index of 1. I require an $index of 2 which corresponds the sub-object position within gapSection.
Is it possible to pass the true $index which corresponds to the $index defined in the original un-filtered object of gapSection? Appreciate any comments on this and thank you!
Object:
var data ={
gapHeader:[{headerId:1,name:"General Requiremets",isApplicable:true},
{headerId:2,name:"Insurance",isApplicable:false}],
gapSection:[{headerId:1,sectionId:1,requirement:"A facility is required to have company structure",finding:null,cmeasures:null,cprocedures:null,personResp:null,isAction:null},
{headerId:2,sectionId:1,requirement:"Organisation must have public liablity",finding:null,cmeasures:null,cprocedures:null,personResp:null,isAction:null},
{headerId:2,sectionId:2,requirement:"Facility must hold workers compensation insurance",finding:null,cmeasures:null,cprocedures:null,personResp:null,isAction:null}]
};
If you need the true index you do not even need to pass the $index property, just pass the object and get the index from the original list.
i.e
$scope.gapSectionFn = function(obj){
var idx = data.gapSection.indexOf(obj);
}
Also it is not clear your issue could really be a nested ng-repeat issue, because according to you gapSection is the inner ng-repeat and you are invoking the call from inner ng-repeat and in need of gapSection's index. It should just be available, but the presence of a DOM filter will just reorg the items and its index which you can also get by doing an ng-init, i.e on the view ng-init="actIndex=$index" and use actIndex.
If you are trying to access parent ng-repeat's index then, ng-init is more appropriate than $parent.$index. Since ng-init is specially designed for that., on the parent ng-repeat you would write ng-init=""parentIndex=$index" and use parentIndex.

ngRepeat:dupes - duplicates in repeater with nested ngrepeat and empty strings

I'm working with angular building a table of data which comes from a JSON API call. I'm having to use a nested ngRepeat however I'm seeing strange results where whole table rows are missing when the row has a couple empty strings.
I can reproduce with the following plunk.
http://plnkr.co/edit/VCzzzPzfgJ95HmC2f83P?p=preview
<script>
function MyController($scope){
$scope.test = {"rows":[
["one","two","three"],
["one","two","three"],
["one","","three"],
["one","",""],
["","two",""],
["","","three"],
["one","two","three"],
["one","two","three"],
]};};
</script>
<div ng-app ng-controller="MyController">
<table>
<tr ng-repeat="(key,ary) in test.rows">
<td>{{key}}</td>
<td ng-repeat="value in ary">{{value}}</td>
</tr>
</table>
</div>
Notice when an array has two empty strings the nested ngRepeat appears to fail.
Am I going mad? Is there an explaination to this?
Yes. You would need to use track by $index since you are repeating primitives, or convert it to array of objects. Reason is ng-repeat creates unique id $$hashkey (and attached to the repeated object as property) for each of the iterated values if it is an object (unless you specify something as track by).
In your case you have primitives so it cannot attach a property itself, so it tries to consider the values repeated as identifier and it finds duplicate when you have multiple empty strings iterated. You would see the same effect when you repeat array of objects with more than one of them is undefined or null as well..
So in this case you can use track by $index So repeated items will be tracked by its index.
<td ng-repeat="value in ary track by $index">{{value}}</td>
Demo
Much better option always is to convert it to array of objects so you don't run into these kinds of issues. WHen you have a property that uniquely identifies the repeated element (say id) you can set it as track by property. When you rebind the array (or refresh the array) angular uses the tracked identifier to determine if it needs to remove the element from DOM and recreate it or just refresh the element that already exists. Many cases where a list is refreshed with the list of items it is always desirable to use a track by with an identifier on the object repeated for performance effectiveness.

Angularjs: ng-repeat, put a ´divider´ in ng-repeat to separate data that was recently loaded

When the page is loaded the user is presented with a set of products, then if he/she clicks a button more products are fetched and appended to the list. I am trying to find an Angular way to put a row to separate the products that were already loaded from the recently appended. I don't want to manipulate the DOM directly and I can't push an empty row to the array of products because the length of the array is used somewhere else.
How could I do this?
Here is a basic Bin
If you don't want a divider after the last item, just use:
<... ng-repeat="...">
<hr ng-show="!$last" />
...
</...>
$last is true if the repeated element is last in the iterator.
I think you'll need to keep track of this yourself, in another $scope (array) property. In your ng-repeat loop, check to see if $index is in this array. If so, add/show the <hr>.
<... ng-repeat="...">
<span ng-show="showHr($index)"><hr></span>
...
</...>
Function showHr() should return true when you want to display an <hr>.
You could also use ng-switch instead of ng-show.

Resources