Angular: fixed table header

http://angularjs.org/

by jacobwsmith

HTML

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css">
<script src="https://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-2.3.0.js"></script>
<div ng-controller="myCtrl">
  <table class="table table-striped table-bordered" nl-fixed-header>
    <thead>
      <tr>
        <th></th>
        <th>Name</th>
        <th>Type</th>
        <th>Type</th>
        <th>Type</th>
        <th>Type</th>
        <th>Type</th>
        <th>Action</th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="item in rows" uib-tooltip="BING BONG">
        <td>{{$index+1}}.</td>
        <td>Name</td>
        <td>Type</td>
        <td>Type</td>
        <td>Type</td>
        <td>Type</td>
        <td>Type</td>
        <td>Action</td>
      </tr>
    </tbody>
  </table>
</div>

CSS

body {
  margin: 50px
}
.table {
  height: 340px;
}

JavaScript

var app = angular.module('app', ['ui.bootstrap']);

// Tooltip config
app.config(['$uibTooltipProvider',
  function($tooltipProvider) {
    $tooltipProvider.options({
      placement: 'top',
      trigger: 'mouseenter',
      animation: false,
      popupDelay: 0,
      appendToBody: true,
      popoverMode: 'single'
    });
  }
]);

// Controller
// TODO: update to component
angular.module('app').controller('myCtrl', function($scope) {
  $scope.rows = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
});


angular.module('app').directive('nlFixedHeader', fixedHeader);
 
    fixedHeader.$inject = ['$timeout'];
 
    function fixedHeader($timeout) {
        return {
            restrict: 'A',
            link: link
        };
 
        function link($scope, $elem, $attrs, $ctrl) {
            var elem = $elem[0];
 
            // wait for data to load and then transform the table
            $scope.$watch(tableDataLoaded, function(isTableDataLoaded) {
                if (isTableDataLoaded) {
                    transformTable();
                }
            });
 
            function tableDataLoaded() {
                // first cell in the tbody exists when data is loaded but doesn't have a width
                // until after the table is transformed
                var firstCell = elem.querySelector('tbody tr:first-child td:first-child');
                return firstCell && !firstCell.style.width;
            }
 
            function transformTable() {
                // reset display styles so column widths are correct when measured below
                angular.element(elem.querySelectorAll('thead, tbody, tfoot')).css('display', '');
 
                // wrap in $timeout to give table a chance to finish rendering
                $timeout(function () {
                    // set widths of columns
                    angular.forEach(elem.querySelectorAll('tr:first-child th'), function (thElem, i) {
 
                        var tdElems = elem.querySelector('tbody...