Callback when ngRepeat/ngIf mutates DOM - javascript

I have a directive that needs to execute a callback function whenever its DOM subtree is mutated (by ngIf or ngRepeat for instance).
The directive is ideally able to be easily inserted to templates I have already made, which rules out putting an ng-init. I've looked at the documentation, and neither ngRepeat or ngIf seem to have any events. Additionally, it seems that most of the browser DOM events have been depreciated as well.
I would use a watch, but I can't think of an expression that will work, as jQuery returns a new object every time and the length of .children() might be unchanged through mutation if ngRepeat removes and inserts a node in the same $digest.
Any suggestions on how to detect any DOM subtree mutation entirely from a template-less directive?
EDIT: For more detail, I have multiple tables that have rows of data inserted with ngRepeat. Whenever a row is inserted, if the table has a resize directive, I need to add CSS to it. The resize directive is general enough to go on every table without needing any input, so I would prefer to not have to add ngInits to all the ngRepeat elements.

For anyone else looking for a solution that will usually work in situations like this, Angular recompiles all the repeated elements, even if they were already present. Thus the actual DOMNode objects are different, so you can just watch a DOMNode.

Related

AngularJS - How to stop ng-repeat when it reaches the last item on object

I ng-repeat a huge object and is working fast on chrome and opera. but on browsers like mozilla and IE, it is very very slow. I tried using pagination and it helped but i would want to display all the items on load instead of paginating them. So i came up with the idea if it is possible to stop ng-repeat from listening to the object after executing the last item on the object.
But it should not remove the html elements that it rendered.
And if the object was changed it should rerun the ng-repeat again and will stop on the last item.
From what i know about ng-repeat, it keeps on looping the object infinitely, so that could be the reason why IE and Mozilla is slow at loading them when the object is too large.
So is this possible to stop ng-repeat?
If your collection isn't changing at all, ngRepeat shouldn't be changing the DOM. You should profile carefully, to make sure the problem is what you think it is.
In the documentation (https://docs.angularjs.org/api/ng/directive/ngRepeat),
ngRepeat uses $watchCollection to detect changes in the collection. When a change happens, ngRepeat then 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.
It isn't possible to prevent this, and $watchCollection (I believe) has to iterate through your collection to see if anything has been reordered.
It also doesn't look like it's possible to 'turn off' the watch while still using ngRepeat.
If you don't want to use $watchCollection as your mechanism for detecting changes, you might have to create your own dirty checking/trigger, and generate the DOM yourself.

Angularjs performance when binding to deeply nested object attributes

When data binding in Angularjs, is there a performance difference (significant or otherwise) between
<div>{{bar}}</div>
and
<div>{{foo.bar}}</div>?
What about <div>{{foo.bar.baz.qux}}</div>?
Context: I am on a team building a large Angularjs application that will potentially have a high volume of data flowing through it, and we would like to avoid the performance hit if there is one.
As i know, the re-evaluation happens within a digest.
Angular iterates through all values in the scope and checks, if the value has changed.
It doesn't look like deep nesting causes much pains there, cuz it's just checking agains the value used in the view. (as long as you dont place a watcher on this deep nested object)
But for some hints:
Don't use methods for conditions within the view:
<span data-ng-hide="someFunction()"></span>
The function will be executed on each digest this may hurt.
Don't use watchers on top of a deep object structure:
Will recursivly run through the whole thing for re-evaluation --> hurts
Use directives instead of {{}}:
Why? Example: angular-translate:
If provides a filter and a directive for the same thing.
<span>{{'WELCOME'|translate}}</span>
<span data-translate="'WELCOME'"></span>
The filter will be re-evaluated on every digest, while the directive has a watcher on that passed value and only re-evaluates, if this code does actually change.
Use data-ng-if instead of ng-Show/Hide (And since the data-ng-if is available):
ng-Show/Hide just makes the DOM elements disappear by using display:none; with css.
The hidden DOM elements will still be evaluated and the data changed, even if it's not visible.
ng-if will completely remove the DOM element, no re-evaluation for stuff within the ng-if

When to favor ng-if vs. ng-show/ng-hide?

I understand that ng-show and ng-hide affect the class set on an element and that ng-if controls whether an element is rendered as part of the DOM.
Are there guidelines on choosing ng-if over ng-show/ng-hide or vice-versa?
Depends on your use case but to summarise the difference:
ng-if will remove elements from DOM. This means that all your handlers or anything else attached to those elements will be lost. For example, if you bound a click handler to one of child elements, when ng-if evaluates to false, that element will be removed from DOM and your click handler will not work any more, even after ng-if later evaluates to true and displays the element. You will need to reattach the handler.
ng-show/ng-hide does not remove the elements from DOM. It uses CSS styles to hide/show elements (note: you might need to add your own classes). This way your handlers that were attached to children will not be lost.
ng-if creates a child scope while ng-show/ng-hide does not
Elements that are not in the DOM have less performance impact and your web app might appear to be faster when using ng-if compared to ng-show/ng-hide. In my experience, the difference is negligible. Animations are possible when using both ng-show/ng-hide and ng-if, with examples for both in the Angular documentation.
Ultimately, the question you need to answer is whether you can remove element from DOM or not?
See here for a CodePen that demonstrates the difference in how ng-if/ng-show work, DOM-wise.
#markovuksanovic has answered the question well. But I'd come at it from another perspective: I'd always use ng-if and get those elements out of DOM, unless:
you for some reason need the data-bindings and $watch-es on your elements to remain active while they're invisible. Forms might be a good case for this, if you want to be able to check validity on inputs that aren't currently visible, in order to determine whether the whole form is valid.
You're using some really elaborate stateful logic with conditional event handlers, as mentioned above. That said, if you find yourself manually attaching and detaching handlers, such that you're losing important state when you use ng-if, ask yourself whether that state would be better represented in a data model, and the handlers applied conditionally by directives whenever the element is rendered. Put another way, the presence/absence of handlers is a form of state data. Get that data out of the DOM, and into a model. The presence/absence of the handlers should be determined by the data, and thus easy to recreate.
Angular is written really well. It's fast, considering what it does. But what it does is a whole bunch of magic that makes hard things (like 2-way data-binding) look trivially easy. Making all those things look easy entails some performance overhead. You might be shocked to realize how many hundreds or thousands of times a setter function gets evaluated during the $digest cycle on a hunk of DOM that nobody's even looking at. And then you realize you've got dozens or hundreds of invisible elements all doing the same thing...
Desktops may indeed be powerful enough to render most JS execution-speed issues moot. But if you're developing for mobile, using ng-if whenever humanly possible should be a no-brainer. JS speed still matters on mobile processors. Using ng-if is a very easy way to get potentially-significant optimization at very, very low cost.
From my experience:
1) If your page has a toggle that uses ng-if/ng-show to show/hide something, ng-if causes more of a browser delay (slower). For example: if you have a button used to toggle between two views, ng-show seems to be faster.
2) ng-if will create/destroy scope when it evaluates to true/false. If you have a controller attached to the ng-if, that controller code will get executed every time the ng-if evaluates to true. If you are using ng-show, the controller code only gets executed once. So if you have a button that toggles between multiple views, using ng-if and ng-show would make a huge difference in how you write your controller code.
The answer is not simple:
It depends on the target machines (mobile vs desktop), it depends on the nature of your data, the browser, the OS, the hardware it runs on... you will need to benchmark if you really want to know.
It is mostly a memory vs computation problem ... as with most performance issues the difference can become significant with repeated elements (n) like lists, especially when nested (n x n, or worse) and also what kind of computations you run inside these elements:
ng-show: If those optional elements are often present (dense), like say 90% of the
time, it may be faster to have them ready and only show/hide them, especially if their content is cheap (just plain text, nothing to compute or load). This consumes memory as it fills the DOM with hidden elements, but just show/hide something which already exists is likely to be a cheap operation for the browser.
ng-if: If on the contrary elements are likely not to be shown (sparse) just build them and destroy them in real time, especially if their content is expensive to get (computations/sorted/filtered, images, generated images). This is ideal for rare or 'on-demand' elements, it saves memory in terms of not filling the DOM but can cost a lot of computation (creating/destroying elements) and bandwidth (getting remote content). It also depends on how much you compute in the view (filtering/sorting) vs what you already have in the model (pre-sorted/pre-filtered data).
One important note:
ngIf (unlike ngShow) usually creates child scopes that may produce unexpected results.
I had an issue related to this and I've spent MUCH time to figure out what was going on.
(My directive was writing its model values to the wrong scope.)
So, to save your hair just use ngShow unless you run too slow.
The performance difference is barely noticable anyway and I am not sure yet on who's favour is it without a test...
If you use ng-show or ng-hide the content (eg. thumbnails from server) will be loaded irrespective of the value of expression but will be displayed based on the value of the expression.
If you use ng-if the content will be loaded only if the expression of the ng-if evaluates to truthy.
Using ng-if is a good idea in a situation where you are going to load data or images from the server and show those only depending on users interaction. This way your page load will not be blocked by unnecessary nw intensive tasks.
ng-if on ng-include and on ng-controller will have a big impact matter
on ng-include it will not load the required partial and does not process unless flag is true
on ng-controller it will not load the controller unless flag is true
but the problem is when a flag gets false in ng-if it will remove from DOM when flag gets true back it will reload the DOM in this case ng-show is better, for one time show ng-if is better

Angular.js change on one item of ng-repeat causing filters on all other items to run

I'm still running into the same problem, filters and functions inside ng-repeat being called all the damn time.
Example here, http://plnkr.co/edit/G8INkfGZxMgTvPAftJ91?p=preview, anytime you change something on a single row, someFilter filter is called 1000 times.
Apparently it's because any change on a child scope bubbles up to its parent, causing $digest to run, causing all filters to run(https://stackoverflow.com/a/15936362/301596). Is that right? How can I prevent it from happening in my particular case?
How can I make it run only on the item that has changed?
In my actual use case the filter is called even when the change is not even on the items of ng-repeat, it's so pointless and it is actually causing performance problems..
// edit cleared all the unnecessary stuff from the plunker
http://plnkr.co/edit/G8INkfGZxMgTvPAftJ91?p=preview
This is just how Angular's dirty checking works. If you have an array of 500 items and the array changes, the filter must be reapplied to the entire array. And now you're wondering "why twice"?
From another answer:
This is normal, angularjs uses a 'dirty-check' approach, so it need to call all the filters to see if exists any change. After this it detect that have a change on one variable(the one that you typed) and then it execute all filters again to detect if has other changes.
And the answer it references: How does data binding work in AngularJS?
Edit: If you're really noticing sluggishness (which I'm not on an older Core 2 Duo PC), there are probably a number of creative ways you can get around it depending on what your UI is going to be.
You could put the row into edit mode while the user is editing the data to isolate the changes, and sync the model back up when the user gets out of edit mode
You could only update the model onblur instead of onkeypress using a directive, like this: http://jsfiddle.net/langdonx/djtQR/1/

this.$el.html vs this.$el.append

Is there a different between this.$el.html and this.$el.append when rendering templates? I'm totally new to js, backbone, etc. In the current project I'm working on, I see stuff like
this.$el.append(Project.Templates["template-library"](this.model))
in the outer view. In this case, this template is for a modal view. Then say the modal view has a row for each item to show in the modal view. Then for each of those rows, the template gets rendered like this:
this.$el.html(this.template({ libraries: libraries.toJSON() }));
Is there any difference between the two? And why append() should be used in certain situations, and html() in the other.
For me it really comes down to how you use your views' render method.
Some people like to use render as an extension of initialize, in that they only use it once, when the view first appears on the page, and often call it from initialize. With this style, you can safely use append without worrying about accidentally adding elements twice, because the render won't get run twice.
Alternatively you can design render to be used over and over again, whenever the view's element needs to change in some way. Backbone supports this style well, eg. this.model.on('change', this.render, this);. For this style, append would be annoying, as you'd constantly have to check whether elements already exist before append-ing them. Instead html makes more sense, because it wipes out whatever was there before.
With append a new element will be inserted into the $el, while html will change the content of the $el.
Using .append() will allow you to add or append something to already existing objects. Rather using .html(), it will change the entire object to new one.

Categories

Resources