简体   繁体   中英

angular performance: critical rendering path?

Im trying to optimice the page loading speed when rendering tables with many rows(columns min. 25x).

I am not experienced debugging/improving performance on angular apps so quite lost on what could be involved in this lack of speed.

Here is Chrome timeline report for 5 row query:

在此输入图像描述

Here is Chrome timeline report for 100 row query:

在此输入图像描述

The XHR load(api/list/json/Chemical...) increases in time as more rows are rendered on the table.

The server response with the data is returned fast(not the bottle neck):

Here is the template for the table:

            <tbody ng-if="compressed">
              <tr ng-if="dbos && (rows.length == 0)">
                <td class="tableColumnsDocs"><div class="tableButtons">&nbsp;</div></td>
                <td class="tableColumnsDocs"><div>No results</div></td>
                <td class="tableColumnsDocs" ng-repeat="attobj in columns track by $index" ng-if="$index > 0">
                  <p>&nbsp;</p>
                </td>
              </tr>
              <tr class="tableRowsDocs" ng-repeat="dbo in rows track by $index">
                <td class="tableColumnsDocs"><div ng-include="'link_as_eye_template'"></div></td>
                <td class="tableColumnsDocs" ng-repeat="attobj in columns track by $index">
                  <div ng-init="values = dbo.get4(attobj.key); key = attobj.key; template = attobj.template || getAttributeTemplate(dbo.clazz + attobj.key);">

                    <div class="content" ng-include="template"></div>
                    <div class="contentFiller" ng-include="template"></div>
                  </div>
                </td>
              </tr>           
            </tbody>

And here templates the table will call:

<script type="text/ng-template" id="plain_values_template">
      <p ng-repeat="v in values track by $index">{{ v }}</p>
</script>

<script type="text/ng-template" id="links_as_dns_template">
      <div ng-repeat="dbo in values track by $index" ng-include="'link_as_dn_template'"></div>
</script>

<script type="text/ng-template" id="json_doc_template">
  <textarea class="form-control" rows="{{values.length + 2}}" ng-trim="false" ng-readonly="true">{{ values | json }}</textarea>
</script>

<script type="text/ng-template" id="link_as_dn_template">
  <a href="#/view/{{ dbo.cid }}"><p>{{ dbo.displayName() }}</p></a>

Relevant controller part:

      $scope.getAttributeTemplate = function(str) {
    //console.log("getAttributeTemplate"); console.log(str);
    if ($templateCache.get(str + ".template")) {
      return str + ".template";
    }
    var a = str.split(/(>|<)/);
    //console.log(a);
    if ((a.length - 1) % 4 == 0) {
      return "links_as_dns_template";
    }
    var clsname = a[a.length - 3];
    if (clsname == "*") {
      return "plain_values_template";
    }
    var attname = a[a.length - 1];
    var cls = datamodel.classes[clsname];
    var att = cls.attribute[attname];
    if (!att) {
      return "plain_values_template";
    }
    if (att.type == "ref") {
      return "links_as_dns_template";
    }
    return "plain_values_template";
  };

在此输入图像描述

I am new to angular and performance opt. so any tips on how to improove or bad practice highlight will be very helpful!

Long tables are angular's biggest evil, because of the hell-as-slow base directives such as ng-repeat

Some easy and obvious stuffs :

I see a lot of bindings in the row/cell templates without one-time binding (::). I dont think your row data is mutating. switching to one-time bindings will reduce the watchers count -> perf.

Some harder stuff :

Quick answer :

dont let angular handle the performance bottleneck

Long answer :

ng-repeat is supposed to compile it's transcluded content once. But using ng-include is killing this effet, causing every row to call compile on their ng-included contents. The key for good performance in big table is to be able to generates (yea, manually, which $compile, $interpolate and stuff) a unique compiled row linking function, with less as possible angular directives - ideally only one-time expression bindings, and to handle row addiction/removal manually (no ng-repeat, you own directive, your own logic)

You should AT LEAST find a way to avoid the second nested ng-repeat on' ng-repeat="attobj in columns track by $index"'. This is a dual repeated on each row, killing compilation &linking (rendering perf) and watcher count (lifecycle perf)

EDIT : as asked, a "naive" example of what can be done to handle the table rendering as manually (and fast) as possible. Note that the example does not handle generating the table header, but it's usually not the hardest thing.

function myCustomRowCompiler(columns) {

    var getCellTemplate = function(attribute) {
        // this is tricky as i dont know what your "getAttributeTemplate" method does, but it should be able to return
        // the cell template AS HTML -> you maybe would need to load them before, as getting them from your server is async.

        // but for example, the naive example to display given attribute would be
        return $('<span>').text("{{::model."+ attribute +"}}"); // this is NOT interpolated yet
    };


    var myRowTemplate = $('<tr class="tableRowsDocs">');

    // we construct, column per column, the cells of the template row
    _.each(columns, function(colAttribute, cellIdx) {
        var cell = $("<td>");
        cell.html(getCellTemplate());
        cell.appendTo(myRowTemplate);
    })

    return $compile(myRowTemplate); // this returns the linking function
}

and the naive usage :

function renderTableRows(dbos, columns) {

    var $scope; // this would be the scope of your TABLE directive
    var tableElement = $el; // this would be your table CONTENT

    var rowLinker = myCustomRowCompiler(columns); // note : in real life, you would compile this ONCE, but every time you add rows.


    for(var i=0; i<dbos; i++) {
        var rowScope = $scope.$new(); // creating a scope for each row
        rowScope.model = dbos[0]; // injecting the data model to the row scope
        rowLinker(rowScope, function(rowClone) { // note : you HAVE to use the linking function second parameter, else it will not clone the element and always use the template
            rowClone.appendTo(tableElement);
        });
    }

};

This is the approach i've been using to my own projects's table framework (well, more advanced, but this is really the global idea), allowing to use angular power to render the cell content ( 'getCellTemplate' implementation can return html with directive, which will be compiled), using filter even including directives in the cell, but keeping the table rendering logic to myself, to avoid useless ng-repeat watch, and minimizing the compilation overheat to it's minimum.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM