AngularJS incredibly slow performance when showing a table (1.6.5) - angularjs

We are building an application to show a table with timetable data.
On the interface the user can set different filters.
I could use a data-grid which would speed up about everything.
I could use a table without grouping and use some sort of lazy fetching which would speed things up.
However we like the layout as is.
The consequence is that the watches are way over 2000 and we are experiencing bottlenecks. It is not that we show hundredths of rows.
How can we make this a bit more performant.
I tried track by, which didn't improve a thing. I tried bind-once but that didn't work either. (Honestly I have no clue how to make it work with key,value objects).
One performance trick might be changing the filters, move and chain them in the controller?
As you also can see, we re-use the same filters a lot, however this is necessary for the group by.
I also haven't seen any lazy-fetching mechanism which works with this kind of custom table / group by.
Hopefully you can help me to point me in the right direction, since I really kinda like the current layout.
The dataset is being displayed in a table and is grouped by date.
Example output:
hrefDateA | hrefDateB | hrefDateC | hrefDateD
DateA
RowA with columns
RowB with columns
RowC with columns
DateB
RowD with columns
RowE with columns
DateC
RowA with columns
RowB with columns
RowC with columns
....
<div ng-if="includeDesktopTemplate" ng-show="whateverdata.length > 0">
<div>
Jump to:
<a ng-href="#tableheader{{$index}}" ng-repeat="(key, value) in whateverdata | filter:filterA() | filter:filterB() | filter:filterC() | groupBy: 'someproperty'" class="someclass">
{{key}}
</a>
</div>
<hr />
<table>
<thead>
<tr>
<th class="timetablerow">HeaderA</th>
<th class="timetablerow">HeaderB</th>
<th class="timetablerow">HeaderC</th>
<th class="timetablerow">HeaderD</th>
<th class="timetablerow">HeaderE</th>
<th class="timetablerow">HeaderF</th>
</tr>
</thead>
<tbody ng-repeat="(key, value) in whateverdata | filter:filterA() | filter:filterB() | filter:filterC() | groupBy: 'someproperty'">
<tr>
<td colspan="6" class="desktoptablegroupby" id="tableheader{{$index}}">
{{key}}
</td>
</tr>
<tr>
<td colspan="6">
<hr class="redbackground" />
</td>
</tr>
<tr ng-repeat="row in value | filter:filterA() | filter:filterB() | filter:filterC()" ng-class-odd="'odd'" ng-class-even="'even'">
<td class="timetablerow">
{{row.propertyA}}
</td>
<td class="timetablerow">
{{row.propertyB}}
</td>
<td class="timetablerow">
{{row.propertyC}} - {{row.propertyD}}
</td>
<td class="timetablerow">
{{row.propertyD}}
</td>
<td class="timetablerow">
{{row.propertyE}}
</td>
<td class="timetablerow">
<div ng-show="{{row.propertyF}}">
<md-tooltip md-direction="{{tooltip.tipDirection}}">
{{row.propertyF}}
</md-tooltip>
<md-icon md-svg-src="~/Content/comment.svg">
</md-icon>
</div>
</td>
</tr>
</tbody>
</table>
<br /><br />
</div>
If I include the code below, watches can go from 3k to 6k
<div ng-show="{{row.propertyF}}">
<md-tooltip md-direction="{{tooltip.tipDirection}}">
{{row.propertyF}}
</md-tooltip>
<md-icon md-svg-src="~/Content/comment.svg">
</md-icon>
</div>
Regarding the code above. One column would show an icon with a tooltip which contains the value of an extra field of the dataset, only when the field contains data. But this also gives issues when the filters are being used (so redraw of screen), since other rows are showing the tooltip then, even when the value of the field of the specific row does not contain a value.(DOM/update/filter issue?)

This is a limitation by the way AngularJS handles change detection and rendering. There's really no easy solution - with emphasis on easy. What I've done on several occasions is use a technique often refered to as virtual-scroll/virtual-repeat. What it basically does is that it only renders the elements that can be seen in the viewport, but adds offsets to the top and bottom of the list/table to keep the scrollbar a constant size, regardless of how many elements are actually rendered. Then whenever you scroll, the elements that pop into view is seamlessly rendered before they become visible. This gives the illusion that it's a single long list/table, when it really only renders what is visible.
There are many libraries that implement this technique. I've personally got experience with the one called angular-vs-repeat but you should take a look at a few and evaluation which fits best your use case. I've also on one occasion implemented my own virtual scroll and it was certainly doable (my usecase in that scenario was that I needed virtual scroll to work both vertically and horizontally).

Related

Automatic scrolling loop for table when content is too big (sub teacher screen)

I've got a screen that displays substitute teacher information for students in two tables for the current day and the next schoolday. On days with lots of absent teachers, there are too many table rows to fit the screen and I would like the table to scroll/loop through the content automatically (to the bottom, and then start with the top again).
I've found code that scrolls up and down, but not for a continuous loop with a table. I've also learned that to loop I need to create a "clone" of the table's content/body, but I haven't gotten much further than that.
Here's the HTML:
<table id="main">
<tr>
<td width="50%">
<table class="half-width-table">
<tr>
<td colspan="5" class="firstrow">TODAY</td>
</tr>
<tr>
<td width="100%" colspan="5" style="margin: 0; padding: 0;"></td>
</tr>
<tr>
<td width="60" class="table_header text_bold">Class</td>
<td width="20" class="table_header text_bold">Period</td>
<td width="40" class="table_header text_bold">Teacher</td>
<td width="40" class="table_header text_bold">Substitute</td>
<td width="40" class="table_header text_bold">Info</td>
</tr>
<tr class="white">
<td>5RS</td><td>2</td><td>RSWI</td><td>LLOR</td><td>Room A41</td>
</tr>
<tr class="grey">
<td>7MC</td><td>5</td><td>TPOR</td><td>NFIE</td><td>Chem, Room S11</td>
</tr>
<!-- This list will often go on and on, usually 40+ items -->
</table>
</td>
<td><!-- The same again for TOMORROW -->
</td>
</tr>
</table>
Auto-scrolling/looping should only be done
if (table_height > window.innerHeight)
(I could get rid of "TODAY" and "TOMORROW" as separate rows in the table by just writing that text aove the table if table-head and tbody are necessary for a solution.)
Any help is much appreciated!
I've found a solution and I'm posting it here, in case someone has the same problem:
I modified the script provided by a user and trincot in this question to suit my needs. I splitted the tables in half, and put the inner and outer div around the content of each table, calling them inner1/outer1 and inner2/outer2.
I adjusted the script so that it would scroll both tables independently. The speed is dependend on the table height, and set in outer1_scroll.
If you want the exact same speed for both tables, remove the "+5000" (which I added to make the table scroll a little faster when there is a lot of content).
The last line ensures that scrolling is only activated if necessary, that is if the table doesn't fit into the window.
var outer1_height = parseInt($(".outer1").height());
var outer1_scroll = outer1_height*20+5000;
function autoScrollDown(){
$(".inner1").css({top:-$(".outer1").outerHeight()})
.animate({top:0},30000,"linear", autoScrollDown);
}
function autoScrollUp(){
$(".inner1").css({top:0}) // jump back
.animate({top:-$(".outer1").outerHeight()},outer1_scroll,"linear", autoScrollUp);
}
$('.outer1').css({maxHeight: $('.inner1').height()});
$('.inner1').html($('.inner1').html() + $('.inner1').html());
if (outer1_height > window.innerHeight) {
autoScrollUp();
}

angular filter without using angular function

What I want to do is to create 2 angular tables where I display tasks. In the first table I want to show all tasks which are not assigned to user and in the second tab I want to show all tasks which are assigned to user
UsersDTO is array, I may have more users assigned to the same task.
My html code looks like this. This is tab where I got all tasks with assigned user to it. I am not pretty sure why this is working, but I assume it somehow looks into property and check if there is anything.
<tbody>
<tr ng-repeat="task in project.ProjectTasksDTO | filter: {UsersDTO: {}}">
<td>{{task.Id}}</td>
<td>{{task.Title}}</td>
<td>{{task.Text}}</td>
<td>{{task.Description}}</td>
<td><p ng-repeat="user in task.UsersDTO">{{user.UserName}}</p></td>
<td>{{task.Status}}</td>
<td>{{task.CreatedBy}}</td>
<td><button class="btn-info" ng-click="editUser(user);">Profile</button></td>
</tr>
</tbody>
Is there a way how to tell to filter in ng-repeat that UsersDTO is null or empty, something like in code below.
<tbody>
<tr ng-repeat="task in project.ProjectTasksDTO | filter: {UsersDTO: {null}}">
<td>{{task.Id}}</td>
<td>{{task.Title}}</td>
<td>{{task.Text}}</td>
<td>{{task.Description}}</td>
<td><p ng-repeat="user in task.UsersDTO">{{user.UserName}}</p></td>
<td>{{task.Status}}</td>
<td>{{task.CreatedBy}}</td>
<td><button class="btn-info" ng-click="editUser(user);">Profile</button></td>
</tr>
</tbody>
Hi you can do it this way without a the need for a function.
<div ng-repeat="task in project.ProjectTasksDTO | filter:{UsersDTO.length: 0}:true">
<div ng-repeat="task in project.ProjectTasksDTO | filter:{UsersDTO.length: 0}:false">
Altough keep in mind that the :true and :false notation is actually an angular specific shorthand notation and will be replaced with function(actual,expected) in a literal comparison to see if these match yes:true or no:false
Fidle

Bootstrap - How to line up two tbody's properly?

I'm having an issue where lining up a table isn't working properly and I was wondering if anyones dealt with something like this before. I looked it up, but couldn't find anything addressing it. I'm using AngularJS and nested ng-repeats which is why I'm having some trouble with this (and need to nest them inside a table). My code is below:
<table class="table table-condensed table-hover table-responsive">
<thead><tr>
<th class="col-sm-4">1</th>
<th class="col-sm-3">2</th>
<th class="col-sm-3">3</th>
</tr></thead>
<tbody ng-repeat="blah in blah">
<tr ng-repeat="blah2 in blah">
<td>......</td>
</tr>
</tbody>
<!--This is the end of "Table 1" in the diagram below-->
<tbody ng-repeat="blah3 in blah4">
<tr ng-repeat="blah5 in blah6">
<td>.........</td>
</tr>
</tbody>
<!--This is the end of "Table 2" in the diagram below-->
</table>
I'm ending up with a result like this (note, I had to draw it due to the fact that the table data I'm using is sensitive information):
How can I pull the second tbody (the smaller one) next to the first?
Thanks
If I understood you correctly, you can simply use Bootstrap's columns as containers for your tables. For instance:
div.col-md-6
Will render two columns together until the screen collapse.
https://jsfiddle.net/DTcHh/11692/
Why not use the grid system of bootstrap ?
<div class="row">
<div class="col-md-4">.col-md-4</div>
<div class="col-md-4">.col-md-4</div>
<div class="col-md-4">.col-md-4</div>
</div>
I don't know what you want to put in the table but you can probably put it into the grid system. link to doc

How to use simple html sentence and mustache object on same line

I am using mustache templating for creating a webpage .I am filling data into a table as
<table style="width:765px;border-collapse: separate;border-spacing: 5px">
<tr>
<td VALIGN=top ALIGN=left style="width:380px">a) <span style="Display:Inline-Block">{{{[0].choice}}}</span></td>
<td VALIGN=top ALIGN=left style="width:380px">b) <span style="Display:Inline-Block">{{{[1].choice}}}</span></td>
</tr>
<tr>
<td VALIGN=top ALIGN=left style="width:380px">c) <span style="Display:Inline-Block">{{{[2].choice}}}</span></td>
<td VALIGN=top ALIGN=left style="width:380px">d) <span style="Display:Inline-Block">{{{[3].choice}}}</span></td>
</tr></table>
My problem is that when the size of data in mustache object is more than 40 character than the whole thing goes in next line like
a)
Hello Here I am Writing Big Text To Experiment It's performance on
Higher Number of Characters
But i want it like
a) Hello Here I am Writing Big Text To Experiment It's performance on
Higher Number of Characters
so i want to know is there any way of getting what i want on making changes on front-end only or i have to add options in database.
What you're seeing here is the behaviour of an inline-block element under circumstances when its content is too long for the space left on the current line - it breaks to the next one.
To get the behaviour you're looking for, you want to use an inline element instead. In other words, leaving the <span> unstyled should be enough; it is by default an inline element:
<table style="width:765px;border-collapse: separate;border-spacing: 5px">
<tr>
<td VALIGN=top ALIGN=left style="width:380px">a) <span>{{{[0].choice}}}</span></td>
<td VALIGN=top ALIGN=left style="width:380px">b) <span>{{{[1].choice}}}</span></td>
</tr>
<tr>
<td VALIGN=top ALIGN=left style="width:380px">c) <span>{{{[2].choice}}}</span></td>
<td VALIGN=top ALIGN=left style="width:380px">d) <span>{{{[3].choice}}}</span></td>
</tr></table>
Here's a JSFiddle to demonstrate a comparison of using inline/inline block in this scenario. Hope this helps! Let me know if you have any questions.
In this Case The Problem Is that The Data coming in mustache is of HTML type i.e. of the form <p> Hello Here I am Writing Big Text To Experiment It's performance on Higher Number of Characters</p> and because of this a new line is coming . so i just removed these p tags from another page which stores the data in database.and now it's coming fine.

Creating and Assign variable value on template

In my view I have a table that will be filled dinamycally with data from server. This table should have at least 5 rows, which means that if my server doesn't return 5 sets of data, the table should be filled with 5 - (n_sets_returned). To make that happen I guess the easy way would be to define a variable and set it to the number of sets returned, but I can't make it. I tried as follow:
<tbody class="center-alignment xcompact" ng-init="cont = 0">
<tr ng-repeat="row in data">
<td>{{row.val1}}</td>
<td>{{row.val2}}</td>
<td>{{row.val3}}</td>
<td>{{row.val5}}</td>
{{cont = $index}}
</tr>
<tr ng-repeat="n in [] | range:(5-cont)">
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
</tbody>
The problem is I don't even know if this would work (probably not) because the empty rows appears before the filled ones, what means Angular obviously doesn't wait for server response to fill the empty rows.
I'm pretty new at Angular. Any help will be appreciated. Thanks.

Resources