title: improve ng-repeat (read only html table) rendering performance in Angular
body: <p>I've read half a dozen articles on improving performance with ng-repeat and so far I can't find a straight forward way at improving rendering of a simple bind once table. </p>
  
  <p>I've profiled various approaches and so far the best I could do was 5 seconds of render time for ~4400 rows. </p>
  
  <p>** a note about this test case. I am using a larger dataset to push performance testing to a challenging dataset. Normally the table would be 1000 rows or less. I'd like to avoid paging as in many cases seeing all data is useful for scanning for anomalies. More importantly the rendering time of 300 rows is also not acceptable to me as it freezes the browser for a short period of time, and that's what i'd like to eliminate. I am fully aware that rendering less data will be faster, I am looking at maximizing performance of larger datasets. </p>
  
  <p>My initial approach </p>
  
  <pre><code>&lt;tbody&gt;
      &lt;tr ng-repeat="f in vm.rowDetails"&gt;
          &lt;td class="checkbox-column"&gt;&lt;input type="checkbox" ng-model="f.selected" /&gt;&lt;/td&gt;
          &lt;td class="w100"&gt;{{f.Code}}&lt;/td&gt;
          &lt;td class="w50"&gt;{{f.Class}}&lt;/td&gt;
          &lt;td&gt;{{f.WebName}}&lt;/td&gt;
          &lt;td&gt;{{f.Category}}&lt;/td&gt;
          &lt;td&gt;{{f.MktgCode}}&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
  </code></pre>
  
  <p>render ~ 7 seconds</p>
  
  <p>Added <strong>bind once</strong> attribute (available since angular 1.3) (although it is not applied to ng-repeat directive</p>
  
  <pre><code>&lt;tbody&gt;
          &lt;tr ng-repeat="f in vm.rowDetails"&gt;
              &lt;td class="checkbox-column"&gt;&lt;input type="checkbox" ng-model="f.selected" /&gt;&lt;/td&gt;
              &lt;td class="w100"&gt;{{::f.Code}}&lt;/td&gt;
              &lt;td class="w50"&gt;{{::f.Class}}&lt;/td&gt;
              &lt;td&gt;{{::f.WebName}}&lt;/td&gt;
              &lt;td&gt;{{::f.Category}}&lt;/td&gt;
              &lt;td&gt;{{::f.MktgCode}}&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
  </code></pre>
  
  <p>no discernible improvement. I suppose this is somewhat expected since this optimizes subsequent watch cycles. </p>
  
  <p>Started experimenting with doing my own row string concatenation. </p>
  
  <pre><code>&lt;tbody&gt;
      &lt;tr pm-table-row f="f" ng-repeat="f in vm.rowDetails track by $index"&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
  </code></pre>
  
  <p>directive:</p>
  
  <pre><code>angular.module('app').directive('pmTableRow', ['$interpolate', function ($interpolate) {
      var template2 = '' +
          '&lt;td class="checkbox-column"&gt;&lt;input type="checkbox" ng-model="f.selected" /&gt;&lt;/td&gt; ' +
                          '&lt;td class="w100"&gt;Code&lt;/td&gt;' +
                          '&lt;td class="w50"&gt;Class&lt;/td&gt;' +
                          '&lt;td&gt;WebName&lt;/td&gt;' +
                          '&lt;td&gt;Category&lt;/td&gt;' +
                          '&lt;td&gt;MktgCode&lt;/td&gt;';
      return {
          restrict: 'A',
          replace: false,
          scope: { f: '=' },
          link: function ($scope, $element, $attr) {
              var fields = ['Code', 'Class', 'WebName', 'Category', 'MktgCode'];
              var t = template2;
              var f = $scope.f;
              for (var k in fields)
                  t = t.replace(fields[k], f[fields[k]]);
  
              $element.html(t);
          }
      }}]);
  </code></pre>
  
  <p>This seemed like an improvement but not a huge one.. render went down to ~4.7 seconds </p>
  
  <p>finally i tried removing ng-repeat completely, and generate the tBody string in my directive</p>
  
  <pre><code>&lt;tbody pm-table-body items="vm.rowDetails"&gt;
  </code></pre>
  
  <p>directive:</p>
  
  <pre><code>angular.module('app').directive('pmTableBody', ['$interpolate', function ($interpolate) {
      var template2 = '' +
          '&lt;tr&gt;&lt;td class="checkbox-column"&gt;&lt;input type="checkbox" ng-model="f.selected" /&gt;&lt;/td&gt; ' +
                          '&lt;td class="w100"&gt;Code&lt;/td&gt;' +
                          '&lt;td class="w50"&gt;Class&lt;/td&gt;' +
                          '&lt;td&gt;WebName&lt;/td&gt;' +
                          '&lt;td&gt;Category&lt;/td&gt;' +
                          '&lt;td&gt;MktgCode&lt;/td&gt;&lt;/tr&gt;';
      return {
          restrict: 'A',
          replace: false,
          scope: { items: '=' },
          link: function ($scope, $element, $attr) {
              var fields = ['Code', 'Class', 'WebName', 'Category', 'MktgCode'];
              var lines = [];
  
              $scope.$watch('items', function () {
                  var items = $scope.items;
                  var t1 = new Date();
                  var t = template2;
                  for (var i = 0; i &lt; items.length; i++) {
                      var f = items[i];
                      for (var k in fields) {
                          t = t.replace(fields[k], f[fields[k]]);
                          lines.push(t);
                      }
                  }
                  console.log('pmTableBody html concatenation done in: ', (new Date() - t1)/1000); // done in 0.02 seconds 
                  $element.html(lines.join(''));
              });
          }
      }}]);
  </code></pre>
  
  <p>for some reason this increased the rendering time to 28 seconds.. so I am likely missing something obvious here. </p>
  
  <p>I would like to bring render time to &lt; 1s. </p>
  
  <p><strong>update</strong> i've removed track by $index by creating a unique id field on my duplicated objects. so far no discernible change in performance but i'll keep trying. </p>
  
  <p><strong>update</strong>
  i've added a watch counter based on <a href="https://stackoverflow.com/questions/18499909/how-to-count-total-number-of-watches-on-a-page">this post</a>. and here are the results: 
  with 4400 rows and ng-repeat approach, there are 26,102 watchers 
  with the tr pm-table-row approach, there are 4457 watchers, and finally with pm-table-body approach, there are 57 watchers! interestingly this is by far the slowest performing approach. </p>
  
  <p><strong>update</strong>
  interestingly after profiling further the pm-table-body approach seemed to use a lot of angular-animate features. 
  <img src="https://i.stack.imgur.com/smRh7.png" alt="enter image description here"></p>
  
  <p>after disabling animation, 10 seconds is taken by <em>(program)</em>
  <img src="https://i.stack.imgur.com/ujRMb.png" alt="enter image description here"></p>
  
  <p>so the browser is still unresponsive for a very long time, still troubleshooting what's happening there. </p>