We had a requirement in an AngularJS portlet to make jQuery DataTable columns dynamic. Normally, DataTable fetches a JSON object that contains the headers and uses that object to instantiate the DataTable. Doing it the Angular way is a bit different. The challenge is adhering to the way Angular does things where it is not advisable to put your service in a directive. Additionally, we want to instantiate the table only after we generate the headers using ng-repeat. Here is my go at it.

For the table we will be working on, the HTML looks like this:

<table id="datatable3" class="table table-striped table-hover" my-datatable aa-data="reportData">
  <thead>
    <tr>
      <th ng-repeat="header in tableHeaders" generate-datatable>{{header.value}}</th>
    </tr>
  </thead>
  <tbody>
  </tbody>
</table>

In your AngularJS state, you can make it look something like this. There’s nothing special about this, other than I opted to use resolve to fetch the table data. That code can also be moved to the AngularJS service.

$stateProvider.state('table', {
  url: '/table',
  templateUrl: 'partial-table.html',
  controller: 'angularDTController',
  resolve: {
    data: function($http){
      return $http({method: 'GET', url: 'data.json'});
    }
  }
})

Create an Angular service that will fetch the headers. We will be injecting this service into the controller.

routerApp.factory('tableService', function($http) {
  return {
    getColumn: function() {
      return $http({
        method: 'GET',
        url: 'columns.json'
      });
    }
  }
});

In the controller, inject the data resolve and tableService.

routerApp.controller('angularDTController', function($scope, tableService, data) {
  $scope.finishedHeader = false;
  //need to get the columns
  var promise = tableService.getColumn();r
  promise.then(function(success) {
    var data = success.data;
    $scope.tableHeaders = data;
  });

  //this one sets the data to the datatable. The reportData is being watched in the directive
  $scope.reportData = data.data.aaData;

});

Note that on line 2, $scope.finishedHeader = false; is what we will be using to tell the directive when to render the datatables, only after the header has been displayed using ng-repeat.

Lastly, create two directives, one for creating the table and one for informing the other that it has generated the last header and the datatable can be generated.

routerApp.directive('myDatatable', function(tableService) {
  function link(scope, element, attrs) {
    scope.$watch('finishedHeader', function(val) {
      if (val === true) {
        var t = $(element).dataTable({
          sDom: '<"clear">TlfCrtip'
        });

        // watch for any changes to our data, rebuild the DataTable
        scope.$watch(attrs.aaData, function(value) {
          var val = value || null;
          if (val) {
            t.fnClearTable();
            t.fnAddData(scope.$eval(attrs.aaData));
          }
        });
      }
    });
  }
  return {
    link: link;
  }
})
  .directive('generateDatatable', function() {
    function link(scope, element, attrs) {
      if (scope.$last) {
        scope.$parent.finishedHeader = true;
      }
    }
    return {
      link: link;
    }
  });

The directive generateDatable sets the finishedHeader scope variable to true when the last header is generated by the ng-repeat. This will trigger the $watch('finishHeader') from the myDatatable directive to start generating the datatable.

You can checkout this plunker for a full demo.

Share This