AngularJS Tables

Learn how to render dynamic, data-driven HTML tables in AngularJS by combining the ng-repeat directive with table rows.

Rendering Tables with ng-repeat

AngularJS builds tables from data, not the other way around. You keep an array of objects on $scope (or on a controller's `this`, referenced as `vm`), place ng-repeat on the <tr> element, and AngularJS clones that row once for every item in the array, keeping the DOM in sync whenever the array changes.

Example

<!DOCTYPE html>
<html>
<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
</head>
<body>

<div ng-app="tableApp" ng-controller="EmployeeController as vm">
  <table border="1" cellpadding="6">
    <thead>
      <tr>
        <th>Name</th>
        <th>Department</th>
        <th>Salary</th>
      </tr>
    </thead>
    <tbody>
      <tr ng-repeat="employee in vm.employees">
        <td>{{ employee.name }}</td>
        <td>{{ employee.department }}</td>
        <td>{{ employee.salary | currency }}</td>
      </tr>
    </tbody>
  </table>
</div>

<script>
angular.module('tableApp', [])
  .controller('EmployeeController', function() {
    var vm = this;
    vm.employees = [
      { name: 'Ava Stone',  department: 'Engineering', salary: 82000 },
      { name: 'Marcus Lee', department: 'Sales',       salary: 61000 },
      { name: 'Priya Nair', department: 'Marketing',   salary: 58000 }
    ];
  });
</script>

</body>
</html>

Inside an ng-repeat, AngularJS exposes a handful of special variables you can use without declaring them yourself. They are most useful for numbering rows and styling alternating rows in a table.

VariableDescription
$indexZero-based index of the current iteration
$firsttrue if this is the first iteration
$lasttrue if this is the last iteration
$middletrue if this is neither the first nor the last iteration
$even / $oddtrue on alternating iterations, useful for row striping
  • Always add a track by expression (for example, track by employee.id) when the array can be re-sorted or filtered, so AngularJS reuses existing DOM rows instead of destroying and rebuilding the whole table.
  • Put ng-repeat on the <tr> itself, never on <table> or <tbody>, so each iteration produces exactly one valid table row.
  • Combine the filter and orderBy filters directly inside the ng-repeat expression to keep searching and sorting reactive without writing extra watchers.

Example

<!DOCTYPE html>
<html>
<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
</head>
<body>

<div ng-app="tableApp" ng-controller="EmployeeController as vm">
<table>
  <tr ng-repeat="employee in vm.employees track by employee.id"
      ng-class="{ 'row-even': $even, 'row-odd': $odd }">
    <td>{{ $index + 1 }}</td>
    <td>{{ employee.name }}</td>
    <td ng-if="$first">First row!</td>
  </tr>
</table>

<style>
  .row-even { background-color: #f4f4f4; }
  .row-odd  { background-color: #ffffff; }
</style>
</div>

<script>
angular.module('tableApp', [])
  .controller('EmployeeController', function() {
    var vm = this;
    vm.employees = [
      { id: 1, name: 'Ava Stone',  department: 'Engineering', salary: 82000 },
      { id: 2, name: 'Marcus Lee', department: 'Sales',       salary: 61000 },
      { id: 3, name: 'Priya Nair', department: 'Marketing',   salary: 58000 }
    ];
  });
</script>

</body>
</html>

Sorting and Filtering Table Data

Because ng-repeat accepts a full AngularJS expression, you can pipe the source array through the built-in filter and orderBy filters right in the template. filter narrows the array down to items matching a search term, and orderBy re-sorts the remaining items by whichever property you point it at.

Example

<!DOCTYPE html>
<html>
<head>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
</head>
<body>

<div ng-app="tableApp">
<div ng-controller="EmployeeController as vm">
  <input type="text" ng-model="vm.search" placeholder="Search by name...">

  <table>
    <tr>
      <th ng-click="vm.sortKey = 'name'">Name</th>
      <th ng-click="vm.sortKey = 'salary'">Salary</th>
    </tr>
    <tr ng-repeat="employee in vm.employees | filter:vm.search | orderBy:vm.sortKey">
      <td>{{ employee.name }}</td>
      <td>{{ employee.salary | currency }}</td>
    </tr>
  </table>
</div>
</div>

<script>
angular.module('tableApp', [])
  .controller('EmployeeController', function() {
    var vm = this;
    vm.employees = [
      { name: 'Ava Stone',  department: 'Engineering', salary: 82000 },
      { name: 'Marcus Lee', department: 'Sales',       salary: 61000 },
      { name: 'Priya Nair', department: 'Marketing',   salary: 58000 }
    ];
  });
</script>

</body>
</html>
Note: Use track by employee.id rather than the default identity tracking whenever a table can be sorted or filtered - it lets AngularJS move existing rows around instead of tearing them down and re-rendering them.
Note: Never use track by $index on a table that can be sorted or filtered. Because $index just counts position, AngularJS will match the wrong data to the wrong row as the order changes, causing stale or mismatched cell content.

Exercise: AngularJS Tables

How are table rows typically generated from an array of data?