Mike Pennington
Mike Pennington

Reputation: 43077

asynchronously populating an AngularJS ngTable with json data

I'm trying to build an AngularJS app, which outputs an HTML table that I populate with json (the table's HTML is at the bottom of this question). I'm using application/json data that I retrieve from my server.

When I do a simple curl http://myurl.local/todo/api/v1/tasks, I get json with no problems. If I put a console.log(); inside this block, I'm clearly getting the json from the server.

        getJson: function() {
            var url = 'http://myurl.local/todo/api/v1/tasks';
            var promise = $http.get(url);
            return promise.then(function(result) {
                console.log("Got data ->" + result.data);
                return result.data;
            });
        }

I'm using Chrome to develop the app; when I run the app in Chrome, Chrome's javascript console throws this error: TypeError: Cannot read property 'length' of undefined before I get to that console.log("Got data ->" + result.data); line. It's a race condition.

The error is very clearly right here:

        $scope.tableParams = new ngTableParams({
            page: 1,            // show first page
            count: 10,          // count per page
        }, {
            total: data.length, // length of data   <--- Broken, needs a promise?

The problem is that javascript isn't my primary language (it's Python); based on the googling I've done about this problem, I think I could fix this with a promise / .then() in the right place. However I'm having difficulty understanding exactly how I should implement this in my javascript. I would like to find the way to fix the async json GET, and understand why it needs to be done that way.

Could someone explain how I should fix this, and why I should do it that way?


The JavaScript:

    var App2 = angular.module('taskTable', ['ngRoute', 'ngTable']);
    // Need to change AngularJS symbols when using flask + Jinja
    App2.config(function($interpolateProvider) {
        $interpolateProvider.startSymbol('[[');
        $interpolateProvider.endSymbol(']]');
    });

    // Thank you PeteBD
    //     http://stackoverflow.com/a/12513509/667301
    // Set up a controller to get json tasks...
    App2.factory('getTasks', function($http) {
        return {
            getJson: function() {
                var url = 'http://myurl.local/todo/api/v1/tasks';
                var promise = $http.get(url);
                return promise.then(function(result) {
                    return result.data;
                });
            }
        }
    });
    App2.controller('tableCntl', function($scope, getTasks, $filter, 
        ngTableParams) {

        var data = [];
        getTasks.getJson().then(function(data) {
            $scope.data = data;
        });
        data = $scope.data;
        // Set up task table parameters
        $scope.tableParams = new ngTableParams({
            page: 1,            // show first page
            count: 10,          // count per page
        }, {
            total: data.length, // length of data
            getData: function($defer, params) {
                // use build-in angular filter
                var orderedData = params.filter() ?
                       $filter('filter')(data, params.filter()) :
                       data;
                // store filtered data as $scope.tasks
                var pageCount = params.page();
                var paramCount = params.count();
                $scope.tasks = orderedData.slice((pageCount-1)*paramCount, 
                    pageCount*paramCount);
                // set total for recalc pagination
                params.total(orderedData.length);
                $defer.resolve($scope.tasks);
            }
        });
    });
    // Props... angular.bootstrap() is required if two apps on the same page
    // http://stackoverflow.com/a/18583329/667301
    angular.bootstrap(document.getElementById("Tasks"),["taskTable"]);

The HTML table via ngTable:

  <div id="Tasks" ng-app="taskTable" ng-controller="tableCntl">
    <p><strong>Filter:</strong> [[tableParams.filter()|json]]
    <table ng-table="tableParams" show-filter="true" class="table">
        <tbody>
          <tr ng-repeat="task in $data">
              <td data-title="'Description'" sortable="description" 
                  filter="{'description': 'text'}">
                  [[task.description]]
              </td>
              <td data-title="'Priority'" sortable="priority" 
                  filter="{'priority': 'text'}">
                  [[task.priority]]
              </td>
              <td data-title="'Entered'" sortable="entry">
                  [[task.entry]]
              </td>
              <td data-title="'Status'" sortable="status">
                  [[task.status]]
              </td>
          </tr>
        </tbody>
    </table>
  </div>

Upvotes: 1

Views: 5892

Answers (1)

rob_james
rob_james

Reputation: 1322

Definitely worth looking at ngResource - this will simply take out all the heavy lifting for you.

On another note, I suspect you don't need to do this:

App2.factory('getTasks', function($http) {
    return {
        getJson: function() {
            var url = 'http://myurl.local/todo/api/v1/tasks';
            var promise = $http.get(url);
            return promise.then(function(result) {
                return result.data;
            });
        }
    }
});

i would change it to :

 App2.factory('getTasks', function($http) {
    return {
        getJson: function() {
            var url = 'http://myurl.local/todo/api/v1/tasks';
            return $http.get(url);
        }
    }
});

As $http returns the promise. That means this bit still works:

    getTasks.getJson().then(function(data) {
        $scope.data = data;
    });

Upvotes: 3

Related Questions