Busata Busata - 1 month ago 6
AngularJS Question

Improving nested ng-repeat + directive performance

I have a horizontal month calendar that shows the activities for employees per day.

Currently it's using a table with the following hierarchy/flow

1) Controller: Generate an array containing every day for that month based on the select month / year. A $scope.$watchCollection updates the array whenever year or month changes.

As the code explains what I do better:

<table class="table table-bordered table-condensed" ng-controller="PlanningOverviewController">
<tr>
<th><input type="text" ng-model="employeefilter"/></th>
<th colspan="7" ng-repeat="week in weeks">
{{week | weekheader}}
</th>
</tr>
<tr>
<th>&nbsp;</th>
<th ng-repeat="day in days">
{{day.format("ddd")}}
</th>
</tr>
<tr>
<th>&nbsp;</th>
<th ng-repeat="day in days">
{{day.date()}}
</th>
</tr>
<tr ng-repeat="employee in planning | filter:employeefilter">
<td> {{employee.firstName}} {{employee.lastName}}</td>
<td class="calendar-unit" ng-repeat="day in days" ng-init="assignments = getAssignments(day, employee)">
<div class="assignment"
assignment ng-repeat="assignment in assignments"
assignment-description = "{{assignment.description}}"
assignment-period="{{assignment.period}}"
assignment-type="{{assignment.type}}">

</div>
</td>
</tr>
</table>


The problem is that the getAssignments() is "expensive" as it needs to check:


  1. Is it a public holiday?

  2. Does the employee work on that day?

  3. Has the employee a holiday?

  4. Get the project assignments for that day



Whenever the list of employee is changed (due to a filter), or the month changes, the generating of the table takes up to 6 seconds when having ~40 employees.

Is there another way to approach this, or to improve the nesting of ng-repeats?

Thanks, can clarify if more info is needed.

EDIT:

I've tried removing some elements from the code to see what causes improvements. When I let assignments return a dummy value, the rendering improves by 2-3 seconds, so some caching there might help it. However, it still takes 3-4 seconds to render the "empty" table that way.

I've then removed logic from the directive itself, effectively making it empty, this still caused the same delay.

However, when removing the directive itself from the html, the table renders almost instantly when changing dates. Do I have to assume that this is a (normal?) limitation to angular? I don't want static data, as the goal is to drag assignments around.

Update 2: I realised that I've been approaching this pretty inefficiently... After looking at the scopes & bindings with batarang + reviewing the code itself, I've noticed that I'm doing way too much "getAssignments()" checks, and that my entire approach is based on "getting assignments for an employee", while it'll probably be better / faster to focus on getting assignments for a day. I'll update the code over the weekend & post an answer if it improves/resolves the issue.

Update 3: After rewriting the way the planning get generated, it now takes 2 seconds to display the table for 50 resources. There are about 1750 directives, (at least 1 event for an employee per day). I assume this is a normal delay seeing the size?

update 4: Code for directive:

angular.module('planning.directives', ["templates.app", "ui.bootstrap", "planning.controllers"])
.directive("assignment", function () {
return {
restrict: "EA",
replace: true,
scope: {
assignmentData: "=",
assignmentCreateMethod: "&"
},
template: '<div class="assignment">{{assignmentData.description}}&nbsp;</div>',
link: function (scope, element, attrs) {

element.addClass(scope.assignmentData.type);

if (scope.assignmentData.period == "am") {
element.css("width", "50%");
element.css("float", "left");
}

if (scope.assignmentData.period == "pm") {
element.css("width", "50%");
element.css("float", "right");
}

element.on("mouseenter",function () {
element.addClass("hover");
}).on("mouseleave", function () {
element.removeClass("hover");
});

element.on("click", function() {
scope.assignmentCreateMethod();
});
}
};
});

Answer Source

In this case it sounds like you're rendering a lot more information that would be able to be visible at any one time. So you could:

  • Rethink the interface with some sort of pagination, so you only render a screen of information at any one time. Or maybe a limit on the number of weeks you can show at any one time.

  • Create some sort of directive that removes its contents (i.e. via an ngIf) if its not visible on the screen. Perhaps listening to (a debounced) scroll event on the window, and calling functions from http://verge.airve.com/ to test if any part of it is visible. It might be tricky to sort out the vertical sizes of the elements in this case, so it slightly depends on your use-case.

    To clarify, this would have to remove the contents before it gets rendered by Angular, and not just hide the elements.