I want to remove watchers from my Angular JS application. To do that, I use the :: keyword to apply one-time binding instead of watchers.
When I want to verify it using Angular Batarang or just plain scope.$$watchers (I think it works in the same way), I see that they still count as watchers.
Why are they appearing on the watchers list? What did I miss?
I think the $watcher is still attached to the value due to the nature of angular binding. But the single binded values won't be reevaluated every $digest cycle. So there is performance improvement using single-bind. See great documentation : one time binding
Related
I'm using Angular Material 1.0.1 library, but I don't want DOM elements' removal to be delayed when using ng-repeat. When the dataset changes, elements stick around for a little bit more and it looks like the page is lagging.
I found out that disabling all animations with $animate.enabled(false) fixes that problem, but still I want some animations, such as for $mdToast to be shown.
How to disable animations only for changes in dataset for ng-repeat?
After doing some research, I think I found my answer.
AngularJS's animations are built to work with CSS transition rules, so I just made the objects disappear with CSS as soon as they were being "animated out".
.repeated .ng-leave {
display: none;
}
This approach works, but still attaches unnecessary animation classes to new objects, which might affect performance. Any suggestions on how to fix this are welcome.
As you already found out, you can use $animate. There is a function that takes the element as argument, so you don't have to disable it globally: $animate.enabled([element], [enabled]);
It should be pretty easy to write a directive which disables the animations on the directive's element.
Alternatively you can probably configure the $animateProvider with $animateProvider.classNameFilter([expression]); to exclude elements with a certain CSS class, the parameter is a RegExp - so something like /^(?:(?!repeated).)*$/ may work (not tested).
If you are after performance then the second approach is probably what you are after. From the docs:
Sets and/or returns the CSS class regular expression that is checked when performing an animation. Upon bootstrap the classNameFilter value is not set at all and will therefore enable $animate to attempt to perform an animation on any element that is triggered. When setting the classNameFilter value, animations will only be performed on elements that successfully match the filter expression. This in turn can boost performance for low-powered devices as well as applications containing a lot of structural operations.
I was wondering why $digest runs all the watches that have been registered on the scope, if you had multiple inputs with data coming from the scope in a view, why does AngularJS have to dirty check all the watches of the scope when for instance just one input field is bound to a label?
Angular is not clairvoyant; it doesn't know what your watchers do except by checking their value. If something may have changed that could potentially mean that displayed data needs to be updated, Angular will check all your watchers. If something never needs to be checked, it shouldn't have a watcher.
Of course it's possible that you have something that only needs to be bound once and won't change after that, which means it doesn't have to be checked constantly. I'm not sure if there's a standard solution for that, but here is a project on Github that provides one-time binding for Angular.
I have read some articles that said ng-repeat would led to poor performance if there is over 2000 items, because there are too many two way binding to watch. I am new to angularjs and have trouble understanding the relationship between ng-repeat and two-way binding:
Does ng-repeat (like outputting a list of json objects) necessarily create two way binding?
Is there a simple way to do ng-repeat using only one way binding? (preferably do not need external module)
Like user1843640 mentioned, if you are on Angular 1.3, you can use one-time-binding, but, just for clarity, you need to put the :: on all the bindings, not just the repeater. The docs say do this:
<div ng-repeat="item in ::items">{{item.name}}</div>
But, if I count the watchers, this only removed one. To really drop the number of two-way-bindings, place the :: on the bindings within the repeater, like this:
<div ng-repeat="item in ::items">{{::item.name}}</div>
Here are two plunkers that will display the number of watchers:
All Bindings
Repeater Only
Thanks goes out to Miraage for provinding the function to count the watchers https://stackoverflow.com/a/23470578/2200446
For anyone using or upgrading to Angular 1.3 you can now use "one-time binding". For ng-repeat it would look like this:
<div ng-repeat="item in ::items">{{item.name}}</div>
Notice the ::items syntax.
For more information check the Angular documentation for expressions.
This blog post presents some interesting solutions. The end result was:
Upgrade to AngularJS 1.1.5 and use limitTo together with Infinite scrolling. AngularJS ng-repeat offers from version 1.1.4 the limitTo option. I slightly adapted the Infinite Scroll directive to make scrolling within a container possible that does not have height 100% of window.
Basically you limit the number of objects you initially render, then use the Infinite scrolling directive to render more as needed. Since you don't want an external module, just mimic the infinite scroll functionality as needed with your own script.
Note: This should solve your performance problem but it won't remove two-way binding.
what ever is generated by ng-model will be having a watcher on data(model) which reduces the page performance if it crosses 200 watchers.
Refer this for ONE WAY BINDING http://blog.scalyr.com/2013/10/31/angularjs-1200ms-to-35ms/ but make sure you use it properly
Hope it helps!!!
Recently migrated to 1.2.0-rc1 and really happy with it. Especially for the ngIf which can be a big performance improvement for rendering, compared to ngShow.
Except there is some flickering when the boolean changes. See the plunkr below for an example:
http://plnkr.co/edit/iYMm0IHZkrRamlaEs9yD?p=preview
On Chrome (v28), you'll see that the first 2 implementations will flicker whereas the 2 others won't.
On Firefox, the flicker happens only rarely.
Is that normal? Is there a way to avoid that? Should I go back to using ngShow?
Edit:
As sza said, ngIf and ngSwitch modify the DOM, whereas ngShow uses a css rule to display/hide elements. In angular 1.0.7, ngSwitch existed already and was already modifying the DOM and was great as we used it a lot in place of ngShow to avoid expensive rendering of things that were not displayed.
Now if you look at http://plnkr.co/edit/pIpEYZ5K8xvfd9dhAC74?p=preview, you'll see that there is no flickering with ngSwitch from 1.0.7.
The answer I'm expecting would be something like "this is normal behaviour, you're using ngSwitch and ngIf where they shouldn't be used, only static content should be displayed with ngSwitch and ngIf" or "this is a rendering bug, live with it until it is fixed". Of course a workaround would be nice too (#timp, I've seen your answer but I'm afraid I'd need a working example).
I asked the same question on the google group, and the answer was that it is a bug in the animation module:
https://groups.google.com/d/msg/angular/Fy-rVdXh5WQ/aqhppEl8Nw0J
Removing the dependency on the animation module will fix the flicker.
If you look into the source code, you will see both ngIf and ngSwitch are all related to DOM manipulation, while ngShowHide only manipulates the CSS rather than DOM.
I guess the DOM manipulation will be a slightly more expensive than CSS change in terms of reflow and it may differ in different browsers.
If you try the ngIf demo on the AngularJS documentation page (link), it has the same issue you described when the checkbox is toggled quickly.
If the improvement in rendering is important, try replacing the checked with two booleans. This way you can specify the order of which ngIf is evaluated first. It's a pain but it does the job.
I need to build a dialog to be used with any item on a list of items. The dialog is pretty much the same regardless of the item except for the values of the fields which are obviously item dependent.
The directive I am building is reading the template from a file, compiles it with $compile and then binds (links) it to the scope of the item. The result of the binding is a DOM tree. To make the dialog visible I need to append this tree to some element in the existing DOM. The nature of my dialog is such that it makes sense to append it directly to the body tag. The dialog will be used many times in combination with different items on the list
So here is my question: How much of this process (compile, bind, append) can be done in advance? I certainly can run compile once. I can also bind the compilation result to the $rootscope and append (hidden) it to the body tag. This way I can later just turn on visibility and show the dialog.
But if it is already bound and attached to DOM, is it kosher to re-bind it to some other scope, if so - what's the right way to do it? Another question is is it even worth it? might be just re-insert it every time it is needed?
If you're only ever going to display one dialog like that at a time and you will use it frequently, you don't have to re-bind it to another scope, just change the data on the scope. Something like this:
Create a service for your dialog
Create the directive and inject your service into it. When the linking function executes, pass something like $scope.dialogData to the service so that the service can update the data.
Create a controller that gets the service injected. Set the dialog data through the service to display the dialog. Since you're modifying data in your controller that's on the directives scope, Angular notices that and updates your dialog.
Add ng-show on your dialogs wrapper to make it simple to implement open()/close() methods on your service.
Now you have a dialog that can be used from anywhere in your system, and you're just re-using the same directive without having to mess with the DOM or compilation.
This is indeed excellent question and I'm happy to see that more and more people are starting to approach dialogs as services.
Regarding your particular questions, here are some of my thoughts:
You can "cache" linking function (that is - function that is returned from the $compile call) and then call this function as needed (passing in scope variables).
Instead of inserting (hidden) compiled element you could only attach it on demand, when a dialog gets opened. On top of this I would rather attach modal element to the $rootElement instead of <body> just not to touch DOM elements above where ng-app was defined. Just not to touch parts of the DOM that AngularJS is not controlling.
IMO dialogs are really close to AngularJS routes (as they provide different "views") and as such it would be very nice to have ability to resolve promises before modal is shown (as with routes).
In fact there are number of things to consider when designing a good, generic dialog service and I hope that those advice, alongside with excellent input provided by others, will get you started. But this all is a bit theoretical so if you are looking at the implementation of what was discussed here you can have a look at this implementation. ($dialog service from http://angular-ui.github.com/bootstrap/ - it is fully customizable so can be used with CSS other than Bootstrap's. Documentation here).
It can be seen in action in this plunk: http://plnkr.co/edit/PG0iHG?p=preview
Excellent question I think. You're wondering if one can "hot swap" the scope of an element. I don't know if there's a way to do that, or even if there is, if that's the Angular way. I take it you looked at how ng-view works to get as far as you've gotten?
My advice is to do the $compile once, keep the result, the link or transclusion function or whatever it's called in Angular parlance, around somewhere. And call it for each needed instance of the dialog.