How does ng-repeat rendering work under the hood? - angularjs

I am an Angularjs rookie. Recently I am reading guide in Angularjs website and get some confusion.
In angularjs API Document about ngRepeat, it says:
If you are working with objects that have an identifier property, you should track by the identifier instead of the whole object. Should you reload your data later, ngRepeat will not have to rebuild the DOM elements for items it has already rendered, even if the JavaScript objects in the collection have been substituted for new ones. For large collections, this significantly improves rendering performance. If you don't have a unique identifier, track by $index can also provide a performance boost.
I don't understand this content:
Should you reload your data later, ngRepeat will not have to rebuild the DOM elements for items it has already rendered, even if the JavaScript objects in the collection have been substituted for new ones.
What this sentence mean? Can you give me an example?
Thank you very much!

Say you have ng-repeat build two elements like this:
<div></div>
<div></div>
Then you add three more elements to the object to get this:
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
The point is, the first two divs won't be removed from the DOM and then recreated and reinserted, but only the three new ones will be added.
The same goes when if the first two elements of your objects were changed, but the element they're being tracked by remains the same (thus they recommend $index) - the two DOM elements (the divs) would still be intact, just their contents replaced.
Of course, no content in the mini-example I gave you, but you get the point. Think of a track by variable as a "glue" that keeps DOM in place.

ngRepeat behavior changes with and without track by.
When the collection changes:
Without track by:
Angular rebuild the DOM (destroy and create again each DOM element).
With track by:
Angular retrieve the DOM element of each item and update the values.
As the doc said:
For large collections, this significantly improves rendering performance.
That behavior is really well explained in this article.

Related

Angular detecting li elements order changes

I'm working with Framework7 (http://framework7.io/) and with Angular.
In my case, I have a sortable list (from F7) filled with an ng-repeat directive.
My problem is when I update my list (LI elements), the $scope array does not change its order.
What I expect is that when I make changes on the list, the $scope array which filled the list, also changes the order of its elements.
How I can obtain the expected behaviour?
I think that with JS/jQuery I can find a workaround, but I want to know which could be a correct solution with Angular.
Many thanks!

Is Angular caching my object?

So I'm trying to build a drag & drop list with ctrl/shift selection in Angular. For some reason, when I attempt to load my template / directive, angular uses the last array loaded at any given point. What I mean by this is, if I load two arrays, each in separate <ul> elements, it will use the second one for both of them. Overall, it's pretty confusing why it happens.
See my fiddle here.
You're initializing the same scope attribute twice:
ng-init="itemList=itemsInTrash"
...
ng-init="itemList=itemsThatAreReal"
So, once the template has been initialized, this itemList variable references the itemsThatAreReal array, and the two included templates thus display this array.

Angular scopes and losing binding

I haven't been able to find anywhere else that really helps me understand my Angular problem here but I suspect it has something to do with inheritance that I haven't completely grasped yet.
I'm doing an ng-repeat of a custom directive and seeing strange behavior when attempting to update the collection from a resource. Here is the plnkr:
http://plnkr.co/edit/QXAFvQix8oUiVMlMuEXw
To see what the issue is, click on the 'Finish Registration' button which retrieves the json from the resource and replaces the current collection. What I expect is for the cards to be replaced by updated cards, but what is happening is that the first 2 cards seem to no longer be bound to the scope (or are somehow zombified) and 2 new cards are added. Each subsequent update updates the new cards as expected. What am I missing?
You are running into an issue with the isolated scope accidentally leaking into other directives defined on the same element.
However, this has been addressed and fixed in Angular v1.2.4 in this commit.
Hence, this version (with only Angular updated) works: http://plnkr.co/edit/kufU5fI90j2lj2jdyLBx?p=preview
The way to write it such that it is AngularJS version agnostic is keeping the relationship between the scopes perfectly clear by nesting the directives:
<div ng-repeat="student in currentUser.studentCollection">
<div student-card
student="student"
unit-schools="unitSchools"></div>
</div>

Angular directive failing silently (directive rendered too soon?)

I have an Angular app with a custom element directive (<uber-table>). <uber-table> is supposed to take a collection of objects, render them into a <table> and add some functionality (click row to toggle the underlying object as selected, search box for live filtering, action links on each row with customized click callback(s) for each object). I created a Plunker with the relevant code. Please note that Plunker is giving an error about map (Object [object Object] has no method 'map'), but locally I am not getting any errors.
The post-link function's element parameter is not the <uber-table> element as I expected. Instead it is the template's <div class="uber-table"> element. This is preventing me from extracting data from <uber-table>. What am I doing wrong? Any help will be much appreciated.
Here's a rundown on some of the issues.
First main issue is you want existing content already within the uber-table markup to exist, as well as a new template. Unless told otherwise the existing content ( columns) in this case will be overwritten. In order to include existing content in a directive that has a template, you need to set transclude:true then identify within template where this existing content needs to be placed using ng-transclude on element that will be parent of the content.
Here's demo with transclude fixed
New problems arise now where you are trying to use jQuery to loop over columns and return angular attrs => column.attrs . This throws undefined error.
I haven't tried to unravel this enough to sort out the columns issues yet. They should likely be handled by directive of their own
UPDATE: here's slightly revised error free version using jQuery to parse column count. I'm afraid am still confused a bit by structure of this. I don't see need to use jQuery to parse colunms, this could be converted to directive or pass column definitions into main directive from controller scope
After trying several things and looking at the documentation again, I finally got it working. The solution was to put the post-link function inside the compile function. Also I had to update my isolated scope to use =, set replace to true and set transclude to 'element'.
I updated Plunker if anybody wants to see the changes. The Plunker version isn't working, but since it is working locally, I'm not going to spend too much time on it.

Directives, Services & ng-repeat

I'm building a complex, variable layout with infinite scroll UI in AngularJS.
Think something like Flipboard with a load of repeating data items, each item containing the same thing (title, description, image etc). Each item is pre-loaded into JSON, sorted and then organised into a series of pages, selected from a series of variable layouts. Each page has a variable number columns and each column a variable number of rows.
In order to achieve this, I'm using multiple Directives for each page layout, compiled at run time. Inside each layout Directive, I'm then building a variable number of columns, each of which contains an ng-repeat for the variable number of rows. Each row can be a different Directive, depending on how it needs to be styled. Again, all of this is happening at runtime.
I'm 99% of the way there.
The Directives are being compiled correctly and calling up the correct column Directives, which in turn are compiling the correct number of rows. Once we get to the row/item level, I'm using a Service which brings back the correct Directive for each item. The reason I choose the Service approach is that I want the items to be reused inside other modules.
The problem I'm having, is that once the page layout is compiled, it sets up the columns and then the rows but does not execute the ng-repeat. I need to get the ng-repeat to loop and also call the specific Directives I use for each item.
I think is a problem to do with $compile and what Angular knows about the DOM. Maybe I need to do a $compile or $apply at some stage to get the ng-repeat to kick in and bind the final Directives to the data items.
**
Side note: if I don't use a Service, but simply use define each layout Directive with a templateUri it works perfectly!
I can go with this approach, but ideally, I'd like to get away from downloading a bunch of templates at runtime. Also, by passing parameters into a Service, I can design more layouts, much quicker and easier than having to build individual template files.
I've set up a JS fiddle with quick example of how I'm approaching all of this:
http://jsfiddle.net/joecarney/vE3Ls/6/
Some SO required code just to post this comment:
<div ng-app="app" ng-controller="appPageController">
<div>
<div my-page></div>
</div>
</div>
If you use scope: {} in a directive it creates a new scope so your directives will not have access to controller scope unless you allow it, which you started out doing but appears you then copied and pasted the same thing for all :)
The directive compiling items in ng-repeat had no access to items. I removed scope from directives and used the attrs value to access the attributes
http://jsfiddle.net/vE3Ls/7/

Resources