<!DOCTYPE html>
<html>

  <head>
    <script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
    <link data-require="bootstrap@3.3.5" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
    <link data-require="font-awesome@4.3.0" data-semver="4.3.0" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" />
    <script data-require="bootstrap@3.3.5" data-semver="3.3.5" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
    <script data-require="angular.js@1.4.6" data-semver="1.4.6" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.min.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
    <script src="directives.js"></script>
    <script src="filters.js"></script>
  </head>

  <body ng-app="app">
    <div ng-controller="TestCtrl">
      <table-sortable columns="columns" 
                      data="rows" 
                      sort="sort">
      </table-sortable>
    </div>
  </body>

</html>
// Code goes here
angular.module('app', ['app.controllers', 'app.directives', 'app.filters']);

angular.module('app.controllers', [])
    .controller('TestCtrl', [
    '$scope', '$timeout', function ($scope, $timeout) {
      
      //Randomly enter data for rows
      $scope.rows = [];
      for(var i = 1; i <= 52; i++){
        $scope.rows.push({
          'Id': i,
          'Name': Math.random().toString(36).substring(7),
          'Amount': Math.floor((Math.random() * 12345)),
          'Date': new Date(new Date() - (Math.floor((Math.random() * 12345)) * 100000000))
        })
      }
      $scope.columns = [
          {
              display: 'Loop Number', //The text to display
              variable: 'Id', //The name of the key that's apart of the data array
              filter: 'number : 0' //The type data type of the column (number, text, date, etc.)
          },
          {
              display: 'Name', //The text to display
              variable: 'Name', //The name of the key that's apart of the data array
              filter: 'text' //The type data type of the column (number, text, date, etc.)
          },
          {
              display: 'Amount', //The text to display
              variable: 'Amount', //The name of the key that's apart of the data array
              filter: 'number : 0' //The type data type of the column (number, text, date, etc.)
          },
          {
              display: 'DateTime', //The text to display
              variable: 'Date', //The name of the key that's apart of the data array
              filter: 'dateTime' //The type data type of the column (number, text, date, etc.)
          }
      ];
      $scope.sort = {
              column: 'Id', //to match the variable of one of the columns
              descending: false
      };
    }
]);
.table-sortable > thead > tr > th {
    cursor: pointer;
    position: relative;
    background-image: none !important;
}
 
.table-sortable > thead > tr > th:after,
.table-sortable > thead > tr > th.sort-false:after,
.table-sortable > thead > tr > th.sort-true:after {
    font-family: FontAwesome;
    padding-left: 5px;
}

.table-sortable > thead > tr > th:after {
    content: "\f0dc";
    color: #ddd;
}
.table-sortable > thead > tr > th.sort-false:after {
    content: "\f0de";
    color: #767676;
}
.table-sortable > thead > tr > th.sort-true:after {
    content: "\f0dd";
    color: #767676;
} 

ul.pagination > li:not(.active){
  cursor: pointer;
}
<table class="table table-hover table-striped table-sortable">
  <thead>
    <tr>
      <th 
        ng-repeat="column in columns"
        ng-class="selectedClass(column.variable)" 
        ng-click="changeSorting(column.variable)">
        {{column.display}}
      </th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="object in data | orderBy:sort.column:sort.descending | orderBy:sort.column:sort.descending | startFrom:currentPage*pageSize | limitTo:pageSize">
      <td  
        ng-repeat="key in object" 
        ng-if="key != '$$hashKey'" >
        {{object[columns[$index].variable] | customFilter : getFilterOfColumn(columns[$index].variable)}}
      </td>
    </tr>
  </tbody>
  <tfoot>
    <tr ng-show="numberOfPages > 1">
      <td colspan="{{columns.length}}">
        <div style="float: left">
            <button class="btn btn-primary" 
                    ng-click="changePage(currentPage - 1)"
                    ng-disabled="currentPage == 0">
              Previous
            </button>
            {{currentPage + 1}} / {{numberOfPages}}
            <button class="btn btn-primary" 
                    ng-click="changePage(currentPage + 1)"
                    ng-disabled="currentPage == numberOfPages - 1">
              Next
            </button>
        </div>
        
        <div class="form-group input-group" style="padding-left: 15px; margin-bottom:0px">
          <div class="input-group-addon">Jump to:</div>
          <select class="form-control"
                  style="max-width: 70px"
                  ng-model="currentPage"
                  ng-change="changePage(currentPage)"
                  ng-options="n+1 for n in [] | range:0:numberOfPages">
          </select>
        </div>
      </td>
    </tr>
    <tr>
        <td colspan="{{columns.length}}" style="border-top: none; padding: 0 10px">
            <ul class="pagination pagination-sm" style="margin: 2px 0">
                <li style="float: left; padding: 6px 12px 6px 0; color: #333333;">Results per page:</li>
                <li ng-repeat="size in pageSizes" ng-if="size < data.length" ng-class="{active: pageSize == size}">
                    <a ng-click="changePageSize(size)"
                       ng-bind="size"></a>
                </li>
                <li ng-class="{active: pageSize >= data.length}">
                    <a ng-click="changePageSize(data.length)">
                        All ({{data.length}})
                    </a>
                </li>
            </ul>
        </td>
    </tr>
  </tfoot>
</table>
angular.module('app.directives', [])
  .directive('tableSortable', ['$timeout', function($timeout) {
    return {
      restrict: 'E',
      transclude: true,
      templateUrl: 'tableSortable.html',
      scope: {
        columns: '=',
        data: '=',
        sort: '='
      },
      link: function(scope, element, attrs) {
        scope.selectedClass = function(columnName) {
          return columnName == scope.sort.column ? 'sort-' + scope.sort.descending : false;
        };
        scope.changeSorting = function(columnName) {
          var sort = scope.sort;
          if (sort.column == columnName) {
            sort.descending = !sort.descending;
          } else {
            sort.column = columnName;
            sort.descending = false;
          }
        };
        scope.getFilterOfColumn = function(columnName) {
          for (var i = 0; i < scope.columns.length; i++) {
            if (columnName == scope.columns[i].variable)
              return scope.columns[i].filter;
          }
          return '';
        };

        scope.pageSizes = [1, 5, 10, 25, 50];
        scope.pageSize = scope.pageSize ? parseInt(scope.pageSize) : 5;
        scope.changePageSize = function(newSize) {
          scope.pageSize = parseInt(newSize);
        };
        scope.currentPage = 0;
        scope.numberOfPages = Math.ceil(scope.data.length / scope.pageSize);
        scope.changePage = function(newPage) {
          scope.currentPage = newPage;
        };

        scope.$watch('pageSize', function() {
          //Recalculate number of pages
          scope.numberOfPages = Math.ceil(scope.data.length / scope.pageSize);

          //If page doesn't exist set to last page
          if (scope.currentPage >= scope.numberOfPages) {
            scope.changePage(scope.numberOfPages - 1);
          }
        });
      }
    }
  }]);
angular.module('app.filters', [])
.filter('customFilter', ['$filter', function($filter) {
  return function (input, filter) {
    if (input == Infinity) return null;
    
    var digits = 2;
    
    if (filter.indexOf(':') > -1) {
      digits = parseInt(filter.split(':')[1].trim());
      filter = filter.split(':')[0].trim();
    }
    
    switch(filter) {
      case 'text':
        return input;
      case 'number':
        return $filter(filter)(input, digits);
      case 'percentage':
        return $filter('number')(input, digits) + '%';
      case 'dateTime':
        return $filter('date')(input, 'MMM d, y h:mm:ss a');
      default:
        return input;
    }
  }
}])
.filter('startFrom', function () {
  return function (input, start) {
    if (!input || isNaN(start))
        return false;
    return input.slice(parseInt(start));
  }
})
.filter('range', function () {
  return function (input, min, max) {
    min = parseInt(min); //Make string input int
    max = parseInt(max);
    for (var i = min; i < max; i++)
        input.push(i);
    return input;
  };
})