var app = angular.module('myApp', ['anguFixedHeaderTable']);
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8" />
<title>AngularJS fixed header scrollable table directive</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" />
<style>
.table {
height: 400px;
}
.nonHeader th {
font-weight: normal !important;
}
</style>
<script src="//code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="//code.angularjs.org/1.2.16/angular.js"></script>
<script src="angu-fixed-header-table.js"></script>
<script src="app.js"></script>
</head>
<body>
<table class="table table-bordered" fixed-header>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
<th>Header 3</th>
<th>Header 4</th>
</tr>
<tr class="nonHeader">
<th>Header 1</th>
<th>Header 2</th>
<th>Header 3</th>
<th>Header 4</th>
</tr>
<tr class="nonHeader">
<th>Header 1</th>
<th>Header 2</th>
<th>Header 3</th>
<th>Header 4</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]">
<td>Row {{item}} Col 1</td>
<td>Row {{item}} Col 2</td>
<td>Row {{item}} Col 3</td>
<td>Row {{item}} Col 4</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Footer 1</td>
<td>Footer 2</td>
<td>Footer 3</td>
<td>Footer 4</td>
</tr>
</tfoot>
</table>
<div class="credits text-center">
<p>
<a href="http://pointblankdevelopment.com.au/blog/angularjs-fixed-header-scrollable-table-directive">AngularJS fixed header scrollable table directive</a>
</p>
<p>
<a href="http://pointblankdevelopment.com.au">pointblankdevelopment.com.au</a>
</p>
</div>
</body>
</html>
/**
* AngularJS fixed header scrollable table directive
* @author Jason Watmore <jason@pointblankdevelopment.com.au> (http://jasonwatmore.com)
* @version 1.1.0
*/
(function () {
angular.module('anguFixedHeaderTable', [])
.directive('fixedHeader', ['$timeout', function ($timeout) {
return {
restrict: 'A',
scope: {
tableHeight: '@'
},
link: function ($scope, $elem, $attrs, $ctrl) {
function isVisible(el) {
var style = window.getComputedStyle(el);
return (style.display != 'none' && el.offsetWidth !=0 );
}
function isTableReady() {
return isVisible(elem.querySelector("tbody")) && elem.querySelector('tbody tr:first-child') != null;
}
var elem = $elem[0];
// wait for content to load into table and to have at least one row, tdElems could be empty at the time of execution if td are created asynchronously (eg ng-repeat with promise)
var unbindWatch = $scope.$watch(isTableReady,
function (newValue, oldValue) {
if (newValue === true) {
// 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 tr:first-child td:nth-child(' + (i + 1) + ')');
var tfElems = elem.querySelector('tfoot tr:first-child td:nth-child(' + (i + 1) + ')');
var columnWidth = tdElems ? tdElems.offsetWidth : thElem.offsetWidth;
if(tdElems) {
tdElems.style.width = columnWidth + 'px';
}
if(thElem) {
thElem.style.width = columnWidth + 'px';
}
if (tfElems) {
tfElems.style.width = columnWidth + 'px';
}
});
// set css styles on thead and tbody
angular.element(elem.querySelectorAll('thead, tfoot')).css('display', 'block')
angular.element(elem.querySelectorAll('tbody')).css({
'display': 'block',
'height': $scope.tableHeight || 'inherit',
'overflow': 'auto'
});
// reduce width of last column by width of scrollbar
var tbody = elem.querySelector('tbody');
var scrollBarWidth = tbody.offsetWidth - tbody.clientWidth;
if (scrollBarWidth > 0) {
// for some reason trimming the width by 2px lines everything up better
scrollBarWidth -= 2;
var lastColumn = elem.querySelector('tbody tr:first-child td:last-child');
lastColumn.style.width = (lastColumn.offsetWidth - scrollBarWidth) + 'px';
}
});
//we only need to watch once
unbindWatch();
}
});
}
};
}]);
})();