AngularJS 1.4 avoid multiple digest calls and maximum call stack - angularjs

I'm working in an angular app where Projects have many Parts, and the data structure is being fetched from the server. In Angular, parts don't have a project attribute, just the project_id. Projects have a commission_rate, and I need access to that in order to calculate the actual commission, and total price for a given part.
My thought was to assign the project to each part when the project is fetched from the server. I tried doing the following:
angular.forEach(project.parts, function(part) {
part.project = project;
});
but I get a massive amount of Max call size stack exceeded, and 10 digest loops reached errors.
I've also tried using $.extend to shallow copy the project and assign that to part.project, but the end result is the same.
Any insight would be greatly appreciated.

Because you only provide this small code fragment all I can do is to tell you what I would look for:
In angular's $digest loop all watcher functions are called and the values are compared with their prior state. Angular than repeats the digest loop to make sure there are no changes introduced by the watcher functions themselves. This loop will be continued until no more changes are detected or the loop counter reaches 10. Than angular gives up. (This mechanism is called dirty-checking)
I would try to change your project object before any bindings apply, this could be inside the success function of your $http promise.

Related

Prevent less time in angular js web application

I have collected all records from angular js batarang but, I don't know how to prevent less time in loading sites and how to know about which range of watcher is correct. Please help me to find this.
Thank you.
Generally 2000 watchers are considered OK per controller. If your watchers count is going above that try to minimize the watchers.
Keeping watchers under limit reduces the time consumed by $digest and $apply cycles and your application remains smooth and responsive.
Watchers are used to track variables defined with $scope. So, use $scope for variables that are going to be used in View or variables were two way binding is required. If you are storing temporary result or variables (that are not going to be changed) use simple JavaScript variables with var.
If your controller is large try to divide the same functionality into 2-3 separate controllers to reduce the watchers at a particular screen.
Use ng-repeat with limit for large arrays.

Delay generating directive until after page is ready and responsive

I'm working on a single-page app where some parts are really slow. They're slow because I'm displaying 400 complex things in a repeater for the user to scroll through. Each thing is generated by a fairly complex directive that does a ton of data binding and expression evaluation, adds one or two click handlers, and displays a couple of images. In some cases, I also need a grayscale CSS filter on those images, but that really seems way too slow.
I have of course already turned most of my data binding into one-time data binding, but simply generating the 400 things for the first time is still slow. It's initially hidden through ng-if, which speeds it up when I'm not showing it, but once I do need to show it, everything waits 10 seconds for that to happen. I would like to load it in advance, but using ng-show instead of ng-if means the loading of the entire app has to wait for this.
What I would like, is to load the rest of the app, and then, while we wait for user input, start creating these 400 things so they're ready once I need to show them. I don't want the user to notice how slow this is.
Problem is, I have no idea how to do this. Any ideas?
Edit: Having thought about this (and discussed this with my wife), I'm seeing two options (that I conceptually understand, at least):
The easy, quick, boring and cowardly solution is to simply not show the 400 things at the same time, but cut them in pieces and show 40 at a time. That should make it a lot quicker, but also less nice, as the user needs to click around to access all the data.
The interesting solution is to write a new ng-repeat that generates the 400 transcluded copies of the template each in their own asynchronous event, so they don't block user interaction. I really like this idea, but there's one big downside: it's an ambitious idea with deep Angular magic, and I don't have much time available.
OK, not seeing your code structure, through Q&A, I'm trying to get clarification. If I understand you correctly, I believe the solution is to process your images asynchronously and remove reliance of creating/processing them on the fly when the view is visible (i.e. via clicking on a button/tab to 'show' the array 'view' and thus triggering the ng-repeat). BTW, this solution assumes the delays are because the images are being processed rather than because they are being shown.
METHOD 1 (less preferred)
To do this, it's best to create an 'ImageDataService' service, where it get's kicked off at application start, and proceeds with building this array and taking whatever time it needs asynchronously without caring what view is showing or not. This service will be injected into the proper view or directive controller--perhaps where the current ng-repeat scope is.
Meanwhile, you also need to change the directives inside your ng-repeat to get the pre-built data from the array provided by ImageDataService rather than actually doing the calculation at view time. If the data for that directive is not ready, the directive will show something like 'loading...' otherwise, the directive will simply show the prebuilt images. This way, it doesn't matter when ng-repeat is triggered (i.e. its view is showing), because it will just show what are processed and ready at that point and the display the rest as 'loading...'.
METHOD 2 (I prefer this)
Alternatively, and maybe better, you can forego creating a pre-processed array in a service as in METHOD 1 and instead modify your directives to process their data asynchronously by invoking a service method that returns an asynchronous promise like this:
(code inside a controller)
var promise = ImageDataService.processImage(directiveData);
promise.then(function() {...set the directive image attributes..})
Needless to say, the ImageDataService.processImage() method needs to return a promise when it is done processing the data. The directive, as usual, will show 'loading...' until then. Also, although ImageDataService no longer needs to pre-populate its array mentioned in METHOD 1, it might be a good idea to save the processed images to a similar array anyway to serve as cache and not reprocess them needlessly. NOTE, in this case you don't need to have processImage() inside a service--but it is really good 'separation of concerns' practice to reserve the work of asynchronous data processing (ajax, etc) within a service which can be injected app-wide rather than within a controller.
Either of these 2 general tacks should work.

Angularjs refresh only angular.element

i working with angularjs, and my web receive data from websocket each second, and now i using a $scope.$apply for refresh every time I get a fact, the problem is when my server send More information than usual the web page is very slow, and if i comment the $scope.$apply the web run fine.
I have something like this
$scope.ChangeData=function(str){
//process.... take around 1ms
//here i change a variable, with new info
$scope.$apply();
//here the process print 60-100ms
}
then i want to do it,
$scope.ChangeData=function(str){
//process.... take around 1ms
//here i change a variable, with new info
angular.element(document.getElementById(str.ID)).scope().$apply
//I do this, I hope the process will only take a few ms
//beacuse i change only the changed element
}
but i have a error in this part
Cannot read property '$apply' of undefined
please help me..
UPDATE
Example in plnkr
Without fully understanding the context of your application, I can only guess that this is a messaging type application. This assumption is based on the fact that a) you're using a socket server to push updates to existing data on the UI and b) you're attempting to maintain existing UI already present.
I took your plunkr and forked it with how I would approach this problem. There are 2 things to realize:
the ItemsController is dumbed down so that it only consumes a model coming from a service and the simulated push to update to said model
the itemsService facilitates
storing the messages data
handles the push update
Because the ng-repeat is already charged with watching any changes in its target collection, you shouldn't have to do any of the $scope.$apply or angular.element stuff you were doing. Each item in the ng-repeat will update the DOM for you. That's its purpose.
https://plnkr.co/edit/qLeDuP0iaTUE16rY2KTl?p=preview

Memory Management in AngularJS

I am new to angular, so maybe I'm going about this completely wrong. I am attempting to make a treeView with angularJS directives. The code that I have so far accomplishes this, except that there appears to be a memory leak as each time the tree view reloads it slows down, and eventually crashes the browser.
I have created the following two directives to accomplish my task jscTreeView and jscTreeNode
This fiddler has my source, it builds you a random tree, and gives you the ability to choose the amount of nodes in the tree. If you bump that number up to a higher number, and reload several times you will notice that it progressively slows down to each time.
Any ideas on how to clean up after myself would be greatly appreciated, thanks.
Edit:
This fiddler is a second attempt with this one I went in a completely different direction. It is much more efficient, and the code is more clean in my opinion. However, this one has an issue too. periodically, and seemingly randomly on refresh of the tree this one throws an infinite digest exception.
Note: not all of the functionality that was in the former tree is in the current tree yet. That is just because I have not programmed it yet.
As the discussion in the comments indicates, I was creating, but never properly releasing, scopes in my tree view. While the solution was not very easy to figure out it is actually quite an easy solution, and clarified things a lot for me.
What I needed to do was make a clone of the root scope of my tree var newScope = scope.$new();, I then built all of the rest of the subtrees as well as their associated nodes and compiled with the cloned scope newScope. After the compile it stores the cloned scope into a variable private to the directive lastScope = newScope;. When the watch property is updated, and calls back to my directive the last cloned scope is destroyed lastScope.$destroy();. Destroying that cloned scope automatically destroys any child scope created beneath it (the nodes, as well as the subtrees). A new clone of the scope is created, and the process is repeated, thanks to #Jorg's tool to count scopes, I can see that everything is being cleaned up nicely on each iteration. Here is a fiddle to the solution.

How to stop a $digest loop apparently caused by a filter

Consider the following Plunker:
http://plnkr.co/edit/10Rs92cRulf9VtI3Bav6
It appears to work correctly, but if you look at the console, you can see that something is wrong. With just that little table, Angular has already hit the Digest loop limit of 10!
it isn't a problem in a small dataset like this one, but the actual production page has 10 different filters and can span 30+ days. As the data grows, the $digest looping is getting way, way out of hand.
What can I do to stop it? I have been pouring over the source code trying to figure out why the Table keeps getting digested over and over and over again, even though the data isn't changing at all. Am I missing something?
Filters are really for massaging data for the view.
The filter on your ng-repeat that loops and creates the date range is causing the issue. ng-repeat is calling that filter every. time. it. 'repeats' :) and from what I can gather you only need to create the range once at start-up (or when data changes). If you move the date range creation code out of a filter it should be more performant. You might even consider moving the range creation server side.
Forked your plunkr http://plnkr.co/edit/XTn7hjbQgV1DxcBlOYVJ.

Resources