5

I've been using the ngTableDynamic directive recently and it works great when I know exactly what columns my table should expect. However, I'm running into issues when my table is truly dynamic and I do not know in advance the number or type of columns to expect.

At the top of my controller I declare the columns I know will be present in every table:

$scope.columns = [
  { title: 'ID', field: 'id', visible: true, required: true },
  { title: 'Email', field: 'email', visible: true, required: true }
];

I then make an asynchronous call to a service which returns results. Based on these results I push any additional column objects into $scope.columns:

var getResults = function() {
  var defer = $q.defer();
  reportService.getReportResults({
    report: $scope.id,
    version: $scope.version
  }).$promise.then(function(data) {
    $scope.data = data.results;
    _.each($scope.data[0], function(value, key) {
      if (key !== 'id' && key !== 'email') {
        $scope.columns.push({
          title: _str.capitalize(key),
          field: key,
          visible: true,
          required: false
        });
      }
    });
    defer.resolve($scope.data);
  });
  return defer.promise;
};

However, the columns pushed within _.each never make it to my table when I view it in the browser. If I replace my asynchronous call with a hard-coded mock of an example set of data however, the columns are added and everything works as expected:

var getResults = function() {
  var defer = $q.defer();
  $scope.data = [{"id":"1","email":"test@sample.com",,"create_date":"2013-09-03T09:00:00.000Z"},{"id":"2","email":"sample@test.org","create_date":"2013-09-03T11:10:00.000Z"}];
  _.each($scope.data[0], function(value, key) {
    if (key !== 'id' && key !== 'email') {
      $scope.columns.push({
        title: _str.capitalize(key),
        field: key,
        visible: true,
        required: false
      });
    }
  });
  defer.resolve($scope.data);
  return defer.promise;
};

Further down in my controller my init code calls

getResults().then(function() {
  $scope.tableParams.reload();
});

$scope.tableParams = new ngTableParams({...

My assumption is that as soon as the promise is resolved $scope.tableParams.reload(); is called before $scope.columns is updated.

MattDionis
  • 3,534
  • 10
  • 51
  • 105
  • 1
    What happens to the hardcoded mock version if you move that `getResults().then(...` call to after you instantiate `$scope.tableParams`? – Will Jul 22 '16 at 20:50
  • 1
    Hi, i'm not too proficient in Angular, but could you please tell me what `$promise` means here? `version: $scope.version }).$promise` I've never seen anything like this before. – Mridul Kashyap Jul 26 '16 at 07:01
  • 1
    @Mridul https://docs.angularjs.org/api/ngResource/service/$resource, find $promise – Asim K T Jul 26 '16 at 10:41
  • 1
    @AsimKT i know about promises. i mean, i have never seen the usage like that. by chaining `$promise`. could you point me to an example maybe? – Mridul Kashyap Jul 26 '16 at 10:45
  • 1
    @Mridul You may not saw it, because most of the time we use $http for our API calls. $promise is with $resource, which gives the original promise object. Read the section on $promise which I shared. Example is also in the doc. – Asim K T Jul 29 '16 at 11:59

3 Answers3

2

Based on demo from documentation it was quite is to make example with promises. It just seems that you have to create new tableParams and cols object after getting response from promise. Adding rows to existing cols may not trigger watcher.

angular.module('app', ['ngTable']);

angular.module('app').controller('Demo', function($scope, NgTableParams, dataService) {
  $scope.cols = [{
    title: 'ID',
    field: 'id',
    visible: true
  }, {
    title: 'Email',
    field: 'email',
    visible: true
  }, {
    title: 'Create Date',
    field: 'create_date',
    visible: true
  }];
  
  dataService
    .getData()
    .then(function (response) {
      $scope.tableParams = new NgTableParams({}, {
        dataset: response.results
      });
      $scope.cols = $scope.cols.concat(response.cols);
    });
});

angular.module('app').factory('dataService', function($timeout) {
  return {
    getData: function() {
      return $timeout(function() {
        var n = Math.round(Math.random() * 50) + 5;
        var results = [];

        for (var i = 0; i < n; i++) {
          results.push({
            id: Math.random(),
            email: 'ex' + Math.random() + '@example.com',
            create_date: new Date(),
            x: Math.round(Math.random() * 10),
            y: Math.round(Math.random() * 25)
          });
        }

        return {
          results: results,
          cols: [
            {
              title: 'X',
              field: 'x',
              visible: true
            },
            {
              title: 'Y',
              field: 'y',
              visible: true
            }
          ]
        };
      }, 500);
    }
  };
});
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular.js"></script>
<link rel="stylesheet" href="https://rawgit.com/esvit/ng-table/master/dist/ng-table.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<script src="https://rawgit.com/esvit/ng-table/master/dist/ng-table.min.js"></script>

<div ng-app="app" ng-controller="Demo">
  <table ng-table-dynamic="tableParams with cols" class="table table-condensed table-bordered table-striped">
    <tr ng-repeat="row in $data">
      <td ng-repeat="col in $columns">{{row[col.field]}}</td>
    </tr>
  </table>
</div>
sielakos
  • 2,406
  • 11
  • 13
1

The reason is that you apply your data to the $scope asynchronously (in the promise then block) so you are outside the angular digest cycle, and your table won't update.

This doesn't happen in your mock because you set $scope.data synchronously. ( A more detailed explanation can be found here )

To fix this, you should notify angular that you've made some changes to the scope with $scope.$digest()

I see some other issues with your code, which I'll try to address

  • bad usage of promises (read: avoid 'defer' whenever you can)
  • no functional approach
  • a little confusion about promises/angular in general

Enough chatter, this is how I'd write your same function:

var getResults = function() {

    // fetch data
    return reportService.getReportResults( {
            report: $scope.id,
            version: $scope.version
        } )
        .then( function( data ) {

            // assign the values
            $scope.data = data.results

            // notify angular to recheck his array
            $scope.$digest()

            // consider using a functional approach to have a cleaner code
            _( $scope.data )
                .first()
                .keys()
                .filter( key => ( key !== 'id' && key !== 'email' ) )
                .each( key => {
                    $scope.columns.push( {
                        title: _str.capitalize( key ),
                        field: key,
                        visible: true,
                        required: false
                    } )
                } )
                .value()

            return $scope.data
        } )
}
Simone Poggi
  • 1,448
  • 2
  • 15
  • 34
0

Try it as

var getResults = function() {
  var defer = $q.defer();
  reportService.getReportResults({
    report: $scope.id,
    version: $scope.version
  }).$promise.then(defer.resolve);
  return defer.promise;
};


 getResults().then(function(_data) {
 $scope.data = _data;
 _.each($scope.data, function(value, key) {
      if (key !== 'id' && key !== 'email') {
        $scope.columns.push({
          title: _str.capitalize(key),
          field: key,
          visible: true,
          required: false
        });
      }
    });

 });

or directly in controller as

    reportService.getReportResults({
    report: $scope.id,
    version: $scope.version
  }).then(function(_data) {
     $scope.data = _data;
     _.each($scope.data, function(value, key) {
          if (key !== 'id' && key !== 'email') {
            $scope.columns.push({
              title: _str.capitalize(key),
              field: key,
              visible: true,
              required: false
            });
          }
        });

     });
Aman Uppal
  • 111
  • 1
  • 6