Angular JS ng-repeat consumes more browser memory - angularjs

I have the following code
<table>
<thead><td>Id</td><td>Name</td><td>Ratings</td></thead>
<tbody>
<tr ng-repeat="user in users">
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td><div ng-repeat="item in items">{{item.rating}}</div></td>
</tr>
</tbody>
</table>
users is an array of user objects with only id and name. number of user objects in array - 150
items is an array of item objects with only id and rating. number of item objects in array - 150
When i render this in browser, it takes about 250MB of heap memory when i tried profiling in my chrome - v23.0.1271.95.
I am using AngularJS v1.0.3.
Is there an issue with angular or am i doing anything wrong here?
Here is the JS fiddle
http://jsfiddle.net/JSWorld/WqSGR/5/

Well it's not the ng-repeat per se. I think it's the fact that you are adding bindings with the {{item.rating}}.
All those bindings register watches on the scope so:
150 * 2 = 300(for the 2 user infos)
150 * 150 = 22500(for the rating info)
Total of 22800 watch functions + 22800 dom elements.
That would push the memory to a conceivable value of 250MB
From Databinding in angularjs
You can't really show more than about 2000 pieces of information to a
human on a single page. Anything more than that is really bad UI, and
humans can't process this anyway.

I want to say the leak is in the second array because you are potentially looping through the same array and displaying every item for every user row in users so depending on how large your test data is that view could get rather large. I could do a little more investigating. btw your fiddle is something entirely different.

Right now you are looping through 150 X 150 = 22500 items. And registering a watch (or through a directive just adding item rating) to each one.
Instead - consider adding the user's rating to the user object itself. It will increase the size of each user object but you will only loop through 150 items and register watches only on them.
Also - consider looking into Indexes. It's apparent that there could be similar users or item ratings. Just index them, so instead of looping through heavy objects, you can reduce them.
One more thing - if you are going to be running the directive the same instance, at least change the code:
var text = myTemplate.replace("{{rating}}",myItem.rating);
to a concat style string calculation:
var text = '<div>' + myItem.rating + '</div>';
This will save you a HUGE chunk on calculation. I've made a JSperf for this case, notice the difference, it's about 99% faster ;-)

Related

How to handle Vue 2 memory usage for large data (~50 000 objects)

I'm trying to implement an table-view for large collections of semi-complex objects on Vue 2. Basically the idea is to collect anywhere between 50 000 to 100 000 rows from DB into JS cache, which is then analyzed dynamically to build table-view with real-time-filters (text-search). Each row within table is toggleable, meaning that clicking the row changes the row to edit-mode, which enables Excel-like editing for that specific field/cell.
Each object has about ~100-150 fields/properties, but only certain amount of 'em are shown at any given moment within table (table columns can be toggled in real-time). For large datasets it seems that DB is pushing about ~10-100mb of JSON-data, which in this use-case is acceptable. Renderwise the performance isn't an issue -- filters work fast enough and only limited amount of results are rendered to DOM.
Everything already works, filters, listing ~100 rows against filters (+ "show 100 more"-mechanism etc), but I hit memory limit when I loaded around 8000 objects into array. This seems to reserve 2 gigabytes of RAM, which after Chrome stops running JS-code all together (even though strangely I'm not getting any kind of warning/error).
I benchmarked memory usage for rows and it seems that ~1000 rows reserves around 300mb of memory. This is most likely reserved by Vue reactivity observers.
Three questions:
Is there a way to toggle reactivity for specific array-list objects (by index or such), so that objects within array itself are unobserved/non-mutable unless specifically called to become mutable (ie. when user clicks row, which enables edit-mode)?
How would you implement handling of large datasets for Vue, as reactivity seems to bottleneck the memory usage? Please do not suggest "limit the results within backend", because it's not the solution which I'm seeking here (even though I may need to create two-part filtering, one for fetching smaller initial dataset and one for realtime filtering). Basically I'm trying to push boundaries of "end of memory" from 8 000 -> 80 000 by re-thinking the data-architecture with Vue. Is the only problem having dataset storaged within Vue's data-variables as reactive?
One idea I have is to turn that "items" -dataset to non-observable/non-reactive with Object.freeze or some similar approach and have table to render two datasets: one for non-reactive and one for those which are currently within edit-mode (which would be pushed to "editableItems" dataset when row is clicked)... any suggestions here (anything simpler, so that I'm able to handle everything within one array?)
I have done similar application on Angular 1, and it handled 50 000 rows quite well, so I'm sure it should be doable within Vue 2 as well... should be just a matter of finding a way on handling reactivity.
Edit 20.4.2021 - Two years later, two years wiser
As this question/answer has gotten lots of attention and is still valid after all the years I wanted to throw few pointers. Most of the details underneath are is still valid. Still, I would direct towards using VueX with Lodash (or modern version of native JS functions) when dealing with filtered results & complex objects.
In order to ease the stress of your backend you can keep things simple: Fetch plain objects without related models. This means that your main results have only ID-keys to related objects. Use Axios or similar library to fetch all the related data with separate AJAX-requests ("customers", "projects", "locations") and use VueX to store 'em in their own list-properties. Create getters for each, such as:
projectsById: state => {
return _.keyBy(state.projects, "id")
},
This way you can use related models for fetching labels and/or full objects when required and your backend doesn't need to fetch related data more than once. States and getters will be available within micro-components as well.
Basically: Avoid fetching full model-trees (even though C# EF or PHP Laravel provide tools for 'em) when dealing with large datasets. Use atomic approach: Fetch 20 different lists ("Axios.all([...])" is your friend!), each with own controller endpoint and cache the results to VueX store... And have fun ;)
Edit 12.03.2019 - additional tips at the end of this answer
It's been a while since I asked this question and I finally got to optimize this part of my project. I'd like to give few pointers for anyone having these performance and/or memory-issues.
Vue documentation never really explained it, but as Andrey pointed out you CAN use the component-object as an data-storage for your custom objects & object-lists. After all, it's just an normal javascript-object.
After optimization my list component setup looks somewhat like this:
module.exports = {
items: [],
mixins: [sharedUtils],
data: function() {
return {
columns: {
all: []
etc... Lot's of data & methods
The items-array is filled with thousands of complex objects (about 80mb of data, 6mb compressed) which I'm handling as non-reactive. This proved to be less of an issue than I would have thought -- Instead of using v-for directly against items I was already using structure in which I triggered filtering of this array whenever user clicked some filter-button and/or inputted string-filtering (such as name). Basically this "processFilters"-method goes through non-responsive items-array and returns filteredItems, which is stored in data-context. Thus it automatically becomes reactive as it's mutated.
<tr v-for="item in filteredItems"
This way all the items within filteredItems stay reactive, but also lose reactivity when they are filtered out, thus saving bunch-load of memory. Whopping 1200mb shrunk to 400mb, which was exactly what I was looking for. Clever!
There are few issues which need to be addressed. Since items doesn't exist in data-context you cannot use it directly within template. This means that instead of writing...
<div v-if="items.length > 0 && everythingElseIsReady">
... I had to store length of items-array to separate data prop. This could have been fixed with computed value as well, but I like to keep those properties existing.
Giving up the reactivity of your main data-array isn't such a bad thing after all - The most important part is to understand that modifications which are made directly against items within that base-array are never triggering any changes to UI and/or sub-components (douh). This shouldn't be such an issue as long as you separate your code in such a way that you have "hidden data container" which holds all the results from backend, and you have smaller (filtered) presentation array of that large container. By using good REST-architecture you should already be good to go with non-reactive data-storage, as long as you remember to check that after saving the item within non-reactive data storage has also been updated to latest revision.
Additionally I was baffled by how little it matters performance-wise how many micro-components there are against hundreds of rows. The render takes a hit obviously, but even if I were to pass large props thousands of times (as I have thousands of instances of input-cells) it didn't seem to hit the memory. One of this kind of objects is my global translations-key/value-pair object, having over 20 000 lines of translated strings... but it still didn't matter. This makes sense, as Javascript uses object-references and Vue Core seems to be properly coded, so as long as you use such configuration objects as props you are simply referring from thousands of objects to the same data-set.
Finally, I'd say start going crazy with complex CRUD objects without fear of hitting memory limit!
Huge thanks for Andrey Popov for giving nudge towards right direction!
Tips (12.03.2019)
Since it's been a while and as I have continued building UI's with large & complex datasets I decided to drop few short ideas & tips.
Consider how you manage your master-records (ie. persons or products) vs related records (sub-objects / relational objects). Try to limit the amount of data injected for subcomponents, as you might be representing the same sub-object multiple times for different master-records. The problem is that it's possible that these objects are not actually reference-objects!
Consider situation where you have person-object, which contains city-object. Multiple persons live in the same city, but when you fetch JSON-data from backend are you sure are those duplicated city-objects actually one and same city (shared/referenced city-object between persons), or multiple representations of similar object (with data being exactly same, but under the hood each one being an individual instance / unique object). Let's say that you have 50 000 persons, each one containing the same sub-object/property "city": { id: 4, name: "Megatown" }, did you just fetch 50 000 individual city instances instead of just one? Is person1.city === person2.city , or do they just look the same and still be two different objects?
If you are unsure whether you are refering to shared city-object or using dozens of instances of similar sub-objects you could simply do there referencing inside your person-list-component. Your person contains city-id, so fetch list of cities with separate REST-method (getCities), and do the pairing on UI-level. This way you have only one list of cities, and you could resolve city from that that list and inject it to person, thus making reference to only one city. Alternatively you could resolve the city from list and pass it as an property to your person-component.
Also make sure to consider what is the purpose of the sub-object. Do you need it to be reactive, or is it static? In order to save bunch of memory you could just tell "person.city = city", which will be injected for each and every person-component, but if it needs to be reactive then you need to use Vue.set -method... and remember that if each city needs to be own instance (so that each person has similar city-object, but properties need to be editable per person) then you need to make sure that you are not using referred object! Thus you most likely need to clone the city-object, which will eat up browsers memory.
Your micro-component might contain separate view-states for both read-only-state and editor-state. This is quite common. Still, you are actually creating instance of that micro-component every time when, thus initializing that component thousands of times.
Think of situation where you have Excel-like spreadsheet with table and table-rows. Each cell contains your custom "my-input" -component, which takes "readonly"-property from your layout. If the UI is on the readonly-state then you are displaying only the label part inside that my-input-component, but otherwise you are displaying input-tag with some special conditions (such as having different input for datetime, number, text, textarea, select-tag etc). Now let's assume you have 100 rows with 20 columns, so you are actually initializing 2000 my-input-components. Now the question is -- what could be improved (performance-wise)?
Well, you could separate readonly-label from my-input-component to your list-view, so that you either display readonly-version (label) OR you you display the editable my-input-component. This way you have v-if condition, which makes sure that those 2000 micro-components won't be initialized unless you have specifically requested to initialize 'em (due either row or whole layout moving from readonly -> editable -state)... You probably guess how big the impact is memory-wise for browser, when Vue doesn't need to create 2000 components.
If you are facing that your page loads really slow it might not be VUE at all. Check out the amount of HTML-tags rendered to your HTML. HTML performs rather poorly when you have large amounts of tags. One of the simplest ways to demonstrate this is by repeating select-tag with 2000 options 100 times, or by having a single 20000 option select-tag. The same way you might be overflowing the amount of html-tags by having lots of micro-components with unnecessary wrapping divs etc... The less depth and less tags you have, the less rendering performance is required from browser & CPU.
Try to learn good HTML-tag architecture via examples. For an example you could study how Trello -services dashboard-view has been programmed. It's quite simple and beautiful representation of rather semi-complex service, with minimal amount of sub-divs.
There are many ways to improve memory handling, but I'd say that most important ones relate to separating "hidden" objects from visible objects, as described on my original answer. Second part is understanding the difference or instanced vs referenced objects. Third is to limit the amount of unnecessary data-passing between objects.
Personally I haven't tried this, but there exists a Vue-virtual-scroller component which handles any amount of data by simply being a wrapper for seemingly infinite amounts of data. Check out the concept # https://github.com/Akryum/vue-virtual-scroller , and let me know if it solved the problem for you.
I hope these guidelines give some ideas for optimizing your components. Never give up the hope, there is always room for improvement!
From everything I've read, I see that you just don't need reactivity for that data, because:
Each row within table is toggleable, meaning that clicking the row changes the row to edit-mode, which enables Excel-like editing for that specific field/cell
This means rows are not editable and data cannot be mutated without user interaction.
Each object has about ~100-150 fields/properties, but only certain amount of 'em are shown at any given moment within table (table columns can be toggled in real-time).
You keep fields reactive but not display them.
And now your questions
Is there a way to toggle reactivity for specific array-list objects (by index or such), so that objects within array itself are unobserved/non-mutable unless specifically called to become mutable (ie. when user clicks row, which enables edit-mode)?
If there's a single item that can be edited at a time, then why keep everything reactive? You can easily use a single variable to listen for that changes.
How would you implement handling of large datasets for Vue, as reactivity seems to bottleneck the memory usage?
It's all about implementation - you rarely end up in a situation when you need a huge list of items to be reactive. The more items you have, the more events needs to happen in order to use the reactivity. If you have 50k items and there are just a few events to mutate (like user modifying data manually), then you can easily listen for those events and make the reactivity manually rather than leave Vue handle all the data. You can check Vuex that can make your life a bit easier for you :)
One idea I have is to turn that "items" -dataset to non-observable/non-reactive with Object.freeze or some similar approach and have table to render two datasets: one for non-reactive and one for those which are currently within edit-mode (which would be pushed to "editableItems" dataset when row is clicked)
This is kind of going in the right direction but there is no need to support two arrays. Imagine using something like this:
data: function() {
return {
editingItem: {}
// when editing is enabled bind the input fields to this item
}
},
created: function() {
this.items = [] // your items, can be used in markdown in the loop, but won't be reactive!
},
watch: {
editingItem: function(data) {
// this method will be called whenever user edits the input fields
// here you can do whatever you want
// like get item's id, find it in the array and update it's properties
// something like manual reactivity ;)
}
}
I had this exact problem where I needed to display a huge list, think 50000 items at least of variable height and I could not find any solution for it
The general solution is to build/use a virtual scroll.
It only keeps a few items in DOM while the rest of them are edited in the DOM. It however keeps changing what is visible depending on whether you scroll up/down
The existing libraries I find do not deal with dynamic heights unless you HARDCODE the heights like vue-virtual-scroller and vue-virtual-scroll-list
vue-collection-cluster allows you to dynamically calculate heights but lags miserably at 50000 items
So I came up with my own solution that scrolls SUPER SMOOTH at 50000+ items, even tested with 100k items and works pretty well
The idea of the implementation for dynamic row heights goes like this
We need to maintain a list of heights for each item in an array
Based on where the scroll Top is we apply a transform translateY vertically to offset the few items that we show the user at all times
I have added ENOUGH comments in the solution for you to easily figure out what is going on
HTML
<script type="text/x-template" id="virtual-list">
<div id="root" ref="root">
<div id="viewport" ref="viewport" :style="viewportStyle">
<div id="spacer" ref="spacer" :style="spacerStyle">
<div v-for="i in visibleItems" :key="i.id" class="list-item" :ref="i.id" :data-index="i.index" #click="select(i.index)" :class="i.index === selectedIndex ? 'selected': ''">
<div>{{ i.index + ' ' + i.value }}</div>
</div>
</div>
</div>
</div>
</script>
<div id="app">
<h1 class="title">
Vue.js Virtual + Infinite Scroll + Dynamic Row Heights + Arrow Key Navigation + No Libraries
</h1>
<p class="subtitle">
No hardcoding of heights necessary for each row. Set emitEnabled to false
for max performance. Tested with <span id="large_num">50000</span> items...
</p>
<div id="list_detail">
<div id="list">
<virtual-list></virtual-list>
</div>
<div id="detail">
<table>
<tbody>
<tr>
<th class="caption">Root Container Height</th>
<td>{{store['root-height']}} px</td>
</tr>
<tr>
<th class="caption">Viewport Height</th>
<td>{{store['viewport-height']}} px</td>
</tr>
<tr>
<th class="caption">Smallest Row Height</th>
<td>{{store['smallest-height']}} px</td>
</tr>
<tr>
<th class="caption">Largest Row Height</th>
<td>{{store['largest-height']}} px</td>
</tr>
<tr>
<th class="caption">Scroll Top</th>
<td>{{store['scroll-top']}} px</td>
</tr>
<tr>
<th class="caption">Page Index</th>
<td>{{store['page-start-index']}}</td>
</tr>
<tr>
<th class="caption">Start Index</th>
<td>{{store['start-index']}}</td>
</tr>
<tr>
<th class="caption">End Index</th>
<td>{{store['end-index']}}</td>
</tr>
<tr>
<th class="caption">Translate Y</th>
<td>{{store['translate-y']}} px</td>
</tr>
</tbody>
</table>
<p><b>Visible Item Indices on DOM</b> {{store['visible-items']}}</p>
<p><b>Total Height Till Current Page</b> {{store['page-positions']}}</p>
<p>
<b>Row's Vertical Displacement From Viewport Top</b>
{{store['row-positions']}}
</p>
<p><b>Heights</b> {{store['heights']}}</p>
</div>
</div>
</div>
CSS
#import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/**
Apply Scroll Bar Styles
https://css-tricks.com/the-current-state-of-styling-scrollbars/
*/
html {
--scrollbarBG: #181C25;
--thumbBG: orange;
}
body::-webkit-scrollbar {
width: 11px;
}
body {
scrollbar-width: thin;
scrollbar-color: var(--thumbBG) var(--scrollbarBG);
}
body::-webkit-scrollbar-track {
background: var(--scrollbarBG);
}
body::-webkit-scrollbar-thumb {
background-color: var(--thumbBG) ;
border-radius: 6px;
border: 3px solid var(--scrollbarBG);
}
html {
height: 100%;
}
body {
min-height: 100%;
height: 100%;
padding: 2rem;
color: #AAA;
background: #181C25;
font-family: 'Open Sans', sans-serif;
font-size: 0.9rem;
line-height: 1.75;
}
#app {
height: 100%;
display: flex;
flex-direction: column;
}
#list_detail {
display: flex;
height: 70%;
}
#list {
flex: 2;
height: 100%;
}
#detail {
flex: 1;
padding: 1rem;
overflow: auto;
height: 100%;
}
#root {
height: 100%;
overflow: auto;
}
.list-item {
padding: 0.75rem 0.25rem;
border-bottom: 1px solid rgba(255, 255, 0, 0.4);
}
.title {
color: white;
text-align: center;
}
.subtitle {
color: orange;
text-align: center;
}
table {
width: 100%;
table-layout: fixed;
text-align: center;
}
th.caption {
text-align: left;
color: #00BEF4;
font-weight: 100;
padding: 0.5rem 0;
}
td {
text-align: left;
}
b{
font-weight: 100;
color: #00BEF4;
}
#large_num {
color: red;
}
.selected {
background: midnightblue;
}
Vue.js
I am getting limited to 30000 characters here on SO and therefore HERE is the complete code on CodePen
Limitations
Does not play nice with screen resize at the moment, working on it
Features
50000+ items effortless scroll
Arrow navigation supported just like a native list
If you have any questions, let me know in the comments
To render large list of components I just found https://github.com/RadKod/v-lazy-component
really love it. It just use the Intersection API to render, or not, the component. It removes the non-visible lazy loaded components and load the visible ones in a very smooth way with no innecesary complications.

Incremen Values in Array in AngularJS

I am a beginner.
trying to make a simple soccer game.
All values are initialized to 0.
select 2 different teams from the dropdown >> enter final score (cannot be negative).
On clicking 'Final score' button,
function should increment the value of 'Played' to 1 for both the teams.
as well as display in the table.
function updateTeam(winner,looser){
if (var a = $scope.Ligateams.lastIndexOf(winner)){
$scope.Ligateams.played = "1";
}
}
Code here
I took a look here, found a few things. I did not change everything, but you should have the tools to make other changes (ex. updating the draw count).
You had two controllers, one was basically acting just to retrieve the list of teams. This complicated the manipulation of the data, so I changed to use $http, and loaded from a .json file.
In your UI, you iterated over the teams, and stored the name. I changed this to store the index for the two teams that were selected. This is used throughout the code to access that team. Since iteration was already occurring in the UI, it made sense to store those indices vs. searching it again in the controller. This grants us access to the object in the teams array with which to increment values.
Relevant bits of code follow.
Load Data via HTTP
$http.get('teams.json').then(function(resp){
$scope.Ligateams = resp.data;
})
Using the index vs. Name
<select ng-model="Matchteam2.idx" ng-disabled="editButton1">
<option ng-repeat="Matchteam2 in Ligateams"
value={{$index}}>{{Matchteam2.name}}
</option>
</select>
Accessing Team info in the Controller
Now we can get the team object from the array of teams with the index.
var team1 = $scope.Ligateams[$scope.Matchteam1.idx];
var team2 = $scope.Ligateams[$scope.Matchteam2.idx];
team1.played++;
team2.played++;
Accessing Team info in the UI
Since the basic 'name' field is no longer on Matchteam#, we can access the name property on the team object in the Ligateams array, again using the index.
Goal by Team <b>{{Ligateams[Matchteam1.idx].name}}</b
Here's a sample with some things fixed: https://plnkr.co/edit/Yby8D0z8IIEGXNxlwX4D?p=preview.

AngularJS: Option in a Select based on saved informations

I'm working on an application where I have to handle vehicules (items) defined by their brand, model and phase. Here is a simple version on Plunker : http://plnkr.co/edit/gA9mzMde4i9hpDfeOdWe
I have created 3 select based on a list of brand. Each brand has a list of models, and each models has a list of phases.
<select ng-options="brand as brand.label for brand in list track by brand.id" ng-model="itemTemp.brand" ng-change="itemTemp.model=''; itemTemp.phase='';">
<option value="">Choose a brand</option>
</select>
When I saved the informations in my database, I realized that I was saving the whole models array in my item.brand and the whole phases array in my item.model, which in reality is way bigger than the example on Plunker. In order to save lighter item in my database, I decided to save only the labels of my brand and model. I reduced the weight of my item by 10.
The problem of doing so is that later when I need to retrieve the informations of the item I saved in my selects, I can't. If I save the whole arrays in item.brand ans item.models, my selects can retrieve the informations and take the correct value.
My question is : Is there any way to have the same result by only saving the labels and not the whole arrays ?
You can retrieve saved data by comparing it with your source array.
Unfortunately it requires looping over data and find the matching entry.
I have edited your Plunker: http://plnkr.co/edit/dC1hMD2ZL79z37kPHAzE?p=preview
$scope.restoreItem = () => {
$scope.itemTemp.brand = findInArray($scope.list, $scope.itemSaved.brand, 'label');
$scope.itemTemp.model = findInArray($scope.itemTemp.brand.models, $scope.itemSaved.model, 'label');
$scope.itemTemp.phase = findInArray($scope.itemTemp.model.phases, $scope.itemSaved.phase);
};

AngularJS ng-repeat performance

I use AngularJS ng-repeat in order to view my table elements (it shouldn't be used very often - I know - but I don't know how to do it in an other way)
Here my example how I'am showing the containerObjects in table:
http://jsfiddle.net/NfPcH/10390/
ng-repeat=...
I have a lot containedObjects (with start, end and containerType) (around 600 per page) which are shown in table.
It took about 3 Seconds to show the view.
My question now would be, if something can be improved in order to improve performance. Is there a possibility to replace/change ng-repeat to decrease loading time.
Thanks a lot!
[EDIT]
I also have this function invocation but I have no idea how to prevent the function invocation. Does have anyone any idea how I could improve this?
Thanks a lot !
ng-repeat="serviceSchedule in getServiceScheduler(institutionUserConnection)">
function getServiceScheduler(institutionUserConnection) {
if(institutionUserConnection.scheduler != null) {
var serviceSchedules = institutionUserConnection.scheduler.serviceSchedules;
return serviceSchedules[Object.keys(serviceSchedules)[0]];
} else {
return null;
}
}
Improvement #1:
As #Petr Averyanov suggested, use a variable instead of a function call.
instead of:
ng-repeat="serviceSchedule in getServiceScheduler(institutionUserConnection)"
change your logic to:
ng-repeat="serviceSchedule in preCalculatedServicesObjectInScope"
To do this, you need other logic changes in controller, you have to manage when the result of getServiceScheduler(institutionUserConnection)" actually changes yourself but it WILL increase the performance.
Improvement #2:
Use variables that are evaluated once if values of rows never change once they are rendered. This will reduce watchers drastically and help you improve overall performance:
```html
<tr ng-repeat="institutionUserConnection in someScopeVariable">
<td>{{ ::institutionUserConnection.user.firstname }} {{ ::institutionUserConnection.user.lastname }}</td>
</tr>```
Improvement #3:
Use track by statement in ng-repeat. Angular.js normally checks/tries to identify which object is which using a special hash function $id in ng-repeat. However, you naturally have an identifier for each object in array (and you should) you can make ng-repeat use this instead of creating it's own id. For example:
<tr ng-repeat="x in someScopeVariable track by x.id">
Improvement #4: (getting hackier here)
Whatever you do, since your array is large, you may not be able to increase the performance enough. Then you should ask this question, do people really see 600 items in one look? No, it won't fit to page. So, you can chop the part that doesn't fit the page, reducing DOM elements to be rendered at once. To do that you can use limitTo filter:
<tr ng-repeat="x in someScopeVariable | limitTo: visual.limitValue">
You can make visual.limitValue a reasonable count such as 10 in the beginning and you can increase it on table's scroll to increase it when use is about to reach the bottom, resulting partial appending of DOM to page. However this hack requires both style and code changes in the page.
Improvement #5: (hackiest one, but it works)
If dimensions of each row is constant, using scroll position and dimension of rows, you can calculate which rows should be visible and you can render them only. I've written a directive for practice, named it uber-repeat and tested it with 60.000 rows and performance was amazing, even in mobile phones!
I think there is a project uses somewhat same approach here: angular-vs-repeat

traversing tables with selenium/webdriverjs

I want to traverse table with Selenium using Node and webdriverJS:
<table>
<tr>
<td class="name">Peter</td>
<td class="count">1</td>
</tr>
<tr>
<td class="name">John</td>
<td class="count">3</td>
</tr>
</table>
I want for every row to look at the names and the rows cells.
What I have:
driver.findElements(By.tagName('tr')).then(function(rows){
// for every row
for (var i = 0; i< rows.length; i++){
// check the name cell
rows[i].findElement(By.class('name')).getInnerHtml().then(function(name){
// do some stuff
});
// check the count cell
rows[i].findElement(By.class('count')).getInnerHtml().then(function(count){
// do some stuff
});
}
});
This works for the first some rows, but with many rows it fails at a certain point.
My theory: the findElement calls in the for-loop are passed to the manager, then the for-loop finishes. Then the garbage collector removes the rows array. Once the manager executes the the findElement calls, the array and its elements do not exist anymore and fail. The error I get is:
StaleElementReferenceException : The Element is not Attached to the DOM
It does work for the first row as the array still exists early-on in the execution.
My questions:
what am I doing wrong?
Is my theory correct?
How can I bind the row[i] references to the findElement calls for them to persist longer than the original array?
---- Edit ----
When I remove one of the inner findElement calls and only look for one cell per row, I am able to cover more rows. This made me think that, with this implementation, time plays a role. This should not be the case, so I am doing probably something wrong.
Is there anything like a forEach function in Selenium?
I found the problem:
I am using a website implemented with Sencha EXTjs.
The table is created on top of a data store: Apparently, the store is called twice and the whole table is recreated in-between the calls.
So I somehow have to wait until the table has loaded for the second time...
This will be the next challenge.

Resources