AngularJS: single view app, large data and memory issue - angularjs

I have single view application that has let's say two columns side by side. On the left you have list of orders, when you click on one, you see the details of it on the right hand side.. You can change quantity and add some details (textfield).. whenever you do that the left column is affected immediately by the change, good.
The app is using one main controller and one service. The orders are stored in $localStorage and passed to $scope by controller when you init the page. The view uses ng-repeat loop on the left column and ng-show on the right. All new orders are pushed from external server to the app (controller updates the $localStorage and $scope on each received update).
The problem starts when the list of orders get big, it's noticeable from 50+, the typing on keyboard (in textarea) is getting slower the more orders are in $scope...
I understand ordering and filtering might by memory consuming, but is this something I should expect or maybe my approach is wrong?
How would you separate your app logic when you need to deal with big data?
(let's say 500+ orders at the same time).
Thanks in advance for all the input, suggestions and your help...

ng-repeat creates lots of watches. Lots of watches = poor performance. How important is the immediate binding to the ng-repeat list? You could create a directive that does what the ng-repeat does with less watches and maybe only updates when you've saved the order.
Also checkout this library... even if it doesn't help you it has a good description of what you're running into and maybe you can figure out a way around it. https://github.com/Pasvaz/bindonce
And what do you mean you're using ng-show to show the orders? That mean you're actually inserting DOM for all the orders "detail view" and then just showing them with ng-show? Because that'd be another huge lag. I'd just have one single "order" partial, perhaps a directive with an isolated scope which can two-way bind to the "selected" order.
It's kinda hard to tell exactly what's going on without any code, but hopefully this helps.

Related

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.

Seamless Integration with REST API

Many examples on the net show you how to use ng-repeat with in-memory data, but in my case I have long table with infinite scroll that gets data by sending requests to a REST API (scroll down - fetch some data, scroll down again - fetch some more data, etc.). It does work, but I'm wondering how can I integrate that with filters?
Right now I have to call a specific method of API service that makes a request based on text in "search" input box and then controller updates $scope.data.
Is it possible to build a custom filter that would do that? And then my view would be utterly decoupled from the service and I could declaratively tell it how to group and order and filter data, regardless if it's in-memory or comes from a remote server, server that can serve only limited records at a time.
Also later I'm gonna need grouping and ordering as well, I'm so tempted to download the entire dataset and lock parts of the app responsible for grouping, searching and ordering (until all data is on the client), but:
a) that dataset is huge (hundred thousands of records)
b) nobody wants to deal with cache invalidation headaches
c) doing so feels so damn wrong, you don't really expect me to 'keep' all that data in-memory, right?
Can you guys point me to maybe some open-source examples where I can steal some ideas from?
Basically I need to build a service and filters that let me to work with my "pageable" data that comes from api, like it's in memory-data.
Regardless of how you choose to solve it (there are many ways to infinite-scroll with angular, here is one: http://binarymuse.github.io/ngInfiniteScroll/), at its latest current beta version, ng-repeat works really bad with large amount of data - so do filters. The reason is obvious - pulling so much information for changes is a tuff job. Moreover, ng-repeat by default will re-draw your complete list every time something changes.
There are many solutions you can explore in this area, here are the ones I found productive:
http://kamilkp.github.io/angular-vs-repeat/#?tab=8
http://www.williambrownstreet.net/blog/2013/07/angularjs-my-solution-to-the-ng-repeat-performance-problem/
https://github.com/allaud/quick-ng-repeat
You should also consider the following, which really helps with large amounts of data.
https://github.com/Pasvaz/bindonce
Updated
I guess you can't really control your server output, because filtering and ordering large amount of data are better off done on the server side.
I was pointing out the links above since even if you write your own filters (and order-bys), which is quite simple to do - http://jsfiddle.net/gdefpfqL/ - (filter by some company name and then click the "Add More" button - to add more items). ordering by is virtually impossible if you can't control the data coming for the server - the only option is getting it all, ordering and then lazy load from the client's memory. So if each of your list items doesn't have many binding by it self (as in the example I've added) - the list item is a fairly simple one (for instance: you simply present the results as a plain text in a <li>{{item.name}}</li> then angular ng-repeat might work for you. In this case, filters will work as expected - say you filter by searched text:
<li ng-repeat="item in items | filter:searchedText"></li>
even for new items added after the user has searched a text, it will still works because the magic of binding.

Prevent AngularJS from dirty checking several thousand rows each $digest cycle

I have a data source that could potentially be several thousand rows. I want users to be able to scroll through the list and filter it without performance problems. My data source automatically fires an event whenever a record changes.
Currently this data is mapped to $rootScope.datasource.
I would like to be able to isolate this large data set from the rest of Angular's $digest cycle and prevent Angular from dirty checking several thousand rows every time something unrelated happens elsewhere in my app. I want Angular to only $digest datasource when I tell it to (after one of my change events fires.)
I've looked at libraries like Bindonce and Watch Fighters. This is a great option if the data is totally static, but my records are going to change occasionally, and I want the UI to update when I tell it to.
Is this currently possible in Angular?
You shouldn't watch large dataset. Instead, watch single value which describes dataset state.
That single value could be incremental counter or hash of you data. If that value changed, watcher listener function will be called.
Another option -- deregistrate watcher after first use and register it if dataset changed.
Also you should try not to use watcher and use $broadcast with updated state.
You could use bindonce and force an update of the UI when you know there were changes. To trigger this update you need to recompile the template. I've done this in this plunker by 'abusing' ng-include, but you could write a special directive that does just that.

how can I exclude an element from an Angular scope?

my premise was wrong. while AngularJS was certainly slowing things down, it was not due to the problem I describe below. however, it was flim's answer to my question - how to exclude an element from an Angular scope - that was able to prove this.
I'm building a site that generates graphs using d3+Raphael from AJAX-fetched data. this results in a LOT of SVG or VML elements in the DOM, depending on what type of chart the user chooses to render (pie has few, line and stacked bar have many, for example).
I'm running into a problem where entering text into text fields controlled by AngularJS brings Firefox to a crawl. I type a few characters, then wait 2-3 seconds for them to suddenly appear, then type a few more, etc. (Chrome seems to handle this a bit better.)
when there is no graph on the page (the user has not provided enough data for one to be generated), editing the contents of these text fields is fine. I assume AngularJS is having trouble when it tries to update the DOM and there's hundreds SVG or VML elements it has to look through.
the graph, however, contains nothing that AngularJS need worry itself with. (there are, however, UI elements both before and after the graph that it DOES need to pay attention to.)
I can think of two solutions:
put the graph's DIV outside the AngularJS controller, and use CSS to position it where it's actually wanted
tell AngularJS - somehow - to nevermind the graph's DIV; to skip it over when keeping the view and model in-sync
the second option seems preferable to me, since it keeps the document layout sane/semantic. is there any way to do this? (or some, even-better solution I have not thought of?)
Have you tried ng-non-bindable? http://docs.angularjs.org/api/ng.directive:ngNonBindable
<ANY ng-non-bindable>
...
</ANY>

How do I create a new, uh, thing in Angularjs?

I think the vagueness of the question is part of the problem, so my real first question is, what do you call, in Angular, the thing.
The thing I'm trying to name is the view plus the controller, over the model of a single object. I don't even know what to call it. For things I know ahead of time I'm going to need, I've been creating directives, but what do you call one instance of the thing that a directive creates?
I have several situations where all of a sudden (in response to some external event), I have a new object in the model and I want to show it on the screen. Angular seems to want me to list all the possible views ab initio in their parent view, but that isn't really reasonable in my case. How, for example, would I list all the pop-ups and tool-tips and other stuff.
I'm down in some little edge case, deep in the controller code, and it needs to add something to the current view. What's the accepted practice.
Incidentally, the $route/ng-view is one case of exactly this. The view containing the ng-view, and the ng-view DIV itself, have no idea what the $route module is going to put in the ng-view. I need the more general case of this strategy.
EDIT
People keep asking for an example. How about this: I'm making an equipment-requisition app. When a user asks that one of the 1000 different type of equipment be sent to him, I need to display a pop-up that gathers addition information specific to that type. If he asks for a screwdriver, the pop-up will ask about blade size, neck length, and handle composition; if he asks for an airplane, it will be a wizard ask him about engine size, fuel tanks, seating arrangement. All the app knows on start-up is the list of all equipment types, and the name of the UI element that gathers all subsequent information about each particular type.
I'm down in some little edge case, deep in the controller code, and it needs to add something to the current view. What's the accepted practice.
Somewhere, you need to define all of the views you'll need -- e.g., all of the equipment popups. You could put each view into a separate file and use ng-include to dynamically pull in the one you currently need to display. Define a property on your $scope (e.g., $scope.equipmentTypeViewUrl), then
<div ng-include src="equipmentTypeViewUrl"></div>
Since ng-view can only appear once per page, ng-include is probably not what you need to use if you need multiple levels of routing.
See also
create a single html view for multiple partial views in angularjs
https://groups.google.com/forum/#!topic/angular/xIIyGpW8KUk/discussion
App design using Angular js
Client Side Template with view per role
I think the problem is that you think that you need to create the "thing" in controller, but actually you don't. The way two-way data binding works is that you change some attribute value, and the view changes based on that. I've never seen a use case where that's not enough, pop-ups and tooltips notwithstanding.
However, if you really must have the controller show something, you could utilize angular's events to do that. You would need two parts: a directive responsible for showing stuff (modifying DOM), and the controller. The controller would $broadcast an event with some parameters, and the directive would listen to those events using $on and react accordingly.
I'd just make sure I had some useful code coming in as the model...
<div class="row" ng-repeat="attribute in attributes">
<div class="widget" ng-repeat="input in attribute.inputs">
<input type="{{input.type}}" ng-model="input.value" />
</div>
</div>
I'm extremely limited in my knowledge, but all I know is if you have a definite structure to your model you can build a view that reacts to it dynamically.
If all of those things are related to your original object (properties or in some way other) you could loop through the data, display the properties and if need use the keys and filters for a label. Imho it's not really an angular question, more one if your data structure. If you have a good data structure you could use a service for creating a related data-object.
For a related popup, you can use a directive and even process the model data there (only recommended if it has a consistent structure).
If you dislike this approach, you can process the data directly in the template.
But without more specific details, there will be no definite answer.

Resources