-1

I have a controller in the provided module that uses data from either a JSON file or an API call. The JSON file version GetActionItems2() works perfectly. Unfortunately, I cannot get the GetActionItems() to work just like its counterpart function (the JSON version). I am using deferred promises and Angular DataTables. No errors in console, but my table has no data in the page.

How do I resolve this?

Controller

angular.module('Action').controller('ActionController', ['$http', '$resource', '$scope', '$state', '$timeout', '$q', 'DTOptionsBuilder', function($http, $resource, $scope, $state, $timeout, $q, DTOptionsBuilder){
        $scope.actionitems = {};    

        function GetActionItems2()
        {
           return $resource('actionitems.json').query().$promise;
        }

        function GetActionItems() {           
            var defer = $q.defer();
            $http.get('api/actionitems')
                 .then(function(response){
                    defer.resolve(response);
                 });
            return defer.promise;
        }   

        $scope.init = function(){
            var vm = this;
            vm.dtOptions = DTOptionsBuilder.fromFnPromise(function() {
                var defer = $q.defer();
                GetActionItems().then(function(result) {
                    $scope.actionitems = result;
                    defer.resolve(result);
                });
                return defer.promise;
            })
            .withPaginationType('full_numbers')
            //.withOption('drawCallback', reload)
            .withDisplayLength(10)
            //.withOption('order', [1, 'desc'])
            .withOption('scrollY', 500)
            .withOption('scrollX', '100%')
            .withDOM('lftrpi')
            .withScroller();
        }           
}]);

Template

<div ng-init="init()" ng-controller="ActionController">
   ActionItems
    <table id="actionitems" class="row-border hover action" datatable="" dt-options="dtOptions">
         <thead>
            <tr>
                <th>
                    ID
                </th>
                <th>
                    Action Item Title
                </th>
                 <th>
                    Criticality
                </th>
                <th>
                    Assignor
                </th>
                <th>
                    Owner
                </th>
                <th>
                    Alt Owner
                </th>
                <th>
                    Approver
                </th>
                <th>
                    Assigned Date
                </th>
                <th>
                    DueDate
                </th>
                <th>
                    ECD
                </th>
                <th>
                    Completion Date
                </th>
                <th>
                    Closed Date
                </th>
                <th>
                    Team
                </th>
                  <th>
                    Meeting
                </th>
                  <th>
                    Phase
                </th>
                  <th>
                    Source
                </th>
            </tr>
         </thead> 
         <tbody>
            <tr ng-repeat="actionitem in actionitems">
               <td>{{actionitem.ActionItemID}}</td> 
               <td>{{actionitem.Title}}</td> 
               <td>{{actionitem.Criticality}}</td>
               <td>{{actionitem.Assignor}}</td> 
               <td>{{actionitem.Owner}}</td>
               <td>{{actionitem.AltOwner}}</td> 
               <td>{{actionitem.Approver}}</td> 
               <td>{{actionitem.AssignedDate}}</td> 
               <td>{{actionitem.DueDate}}</td> 
               <td>{{actionitem.ECD}}</td> 
               <td>{{actionitem.CompletionDate}}</td> 
               <td>{{actionitem.ClosedDate}}</td> 
         </tbody>     
    </table>
</div>
Vahe
  • 1,699
  • 3
  • 25
  • 76
  • Avoid using the [deferred Anti-pattern](https://stackoverflow.com/questions/30750207/is-this-a-deferred-antipattern). – georgeawg Apr 18 '19 at 06:54

3 Answers3

1

Returning $http.get('api/actionitems').then(function(result) to fromFnPromise with an embedded return result.data inside of function(result) resolved the issue and avoided usage of the deferred Anti-pattern.

$scope.init = function() {
    var vm = this;
    vm.dtOptions = DTOptionsBuilder.fromFnPromise(function() {
        return $http.get('api/actionitems').then(function(result) {
            $.each(result.data, function(key, actionitem) {
                result.data[key] = [
                    actionitem.actionitemid,
                    actionitem.actionitemtitle,
                    actionitem.criticality,
                    actionitem.assignor,
                    actionitem.owner,
                    actionitem.altowner,
                    actionitem.approver,
                    actionitem.assigneddate,
                    actionitem.duedate,
                    actionitem.ecd,
                    actionitem.completiondate,
                    actionitem.closeddate
                ];
            });
            $scope.actionitems = result.data;
            return result.data;
        });
    })
    .withPaginationType('full_numbers')
    //.withOption('drawCallback', reload)
    .withDisplayLength(10)
    //.withOption('order', [1, 'desc'])
    .withOption('scrollY', 500)
    .withOption('scrollX', '100%')
    .withDOM('lftrpi')
    .withScroller();
}
Vahe
  • 1,699
  • 3
  • 25
  • 76
  • 1
    This correctly avoids the Deferred Anti-Pattern. I would use [angular.forEach](https://docs.angularjs.org/api/ng/function/angular.forEach) instead of `$.each` but this answer is functionally correct. – georgeawg Apr 19 '19 at 00:09
0

If this asynchronous processing comes from a third party, you may need to manually trigger a data update for that controller.

// ...
$scope.init = function(){
   var vm = this;
   vm.dtOptions = DTOptionsBuilder.fromFnPromise(function() {
       var defer = $q.defer();
       GetActionItems().then(function(result) {
           $scope.actionitems = result;

           // here trigger a data update
           $scope.$digest();

           defer.resolve(result);
       });
    return defer.promise;
})
// ...

The $digest method is to manually trigger the change detection of the controller.

yujinpan
  • 519
  • 3
  • 6
  • Let me give this a shot, I have an internal api that is along side my AngularJS app. – Vahe Apr 18 '19 at 01:40
  • Looks like digest was already in progress, my answer covers the modifications made to work, I still wonder why datatables requires a keyless array for each 'row' or item in the dataset. – Vahe Apr 18 '19 at 03:48
  • Avoid using the [deferred Anti-pattern](https://stackoverflow.com/questions/30750207/is-this-a-deferred-antipattern). – georgeawg Apr 18 '19 at 06:55
  • Also `GetActionsItems()` returns an $http promise. The `$scope.digest()` is not necessary. In fact it generates an [Error: $rootScope:inprog Action Already In Progress](https://docs.angularjs.org/error/$rootScope/inprog). – georgeawg Apr 18 '19 at 13:55
-1

SEE EDIT BELOW

My initial solution was a two step process to get data into my table.

I introduced a custom apply function then a getRes function which then populated array of actionitems with individual actionitems with only an array of values to be sent off to datatables and asign to a $scope.acionitems. Otherwise I would get an alert from datatables that regarding the format of each item being in JSON format with corresponding keys.

angular.module('Action').controller('ActionController', ['$http', '$resource', '$scope', '$state', '$timeout', '$q', 'DTOptionsBuilder', function($http, $resource, $scope, $state, $timeout, $q, DTOptionsBuilder){
        $scope.actionitems = {};    

        function GetActionItems2()
        {
           return $resource('actionitems.json').query().$promise;
        }

        function GetActionItems() {           
            var defer = $q.defer();
            $http.get('api/actionitems')
                 .then(function(response){
                     defer.resolve(response);
                 });
            return defer.promise;
        }   

        var getRes = function(res){
            $.each(res, function(key, actionitem){
                res[key] =  [ 
                                actionitem.actionitemid, 
                                actionitem.actionitemtitle,
                                actionitem.criticality,
                                actionitem.assignor,
                                actionitem.owner,
                                actionitem.altowner,
                                actionitem.approver,
                                actionitem.assigneddate,
                                actionitem.duedate,
                                actionitem.ecd,
                                actionitem.completiondate,
                                actionitem.closeddate
                            ];           

            });
            $scope.actionitems = res;
        }

        function apply(scope, fn, res) {
            (scope.$$phase || scope.$root.$$phase) ? 
                        fn(res) : 
                        scope.$apply(fn(res));
        }

        $scope.init = function(){
            var vm = this;
            vm.dtOptions = DTOptionsBuilder.fromFnPromise(function() {
                var defer = $q.defer();

                GetActionItems().then(function(result){
                    apply($scope, getRes, result.data);
                    defer.resolve(result.data);
                });

                return defer.promise;
            })
            .withPaginationType('full_numbers')
            //.withOption('drawCallback', reload)
            .withDisplayLength(10)
            //.withOption('order', [1, 'desc'])
            .withOption('scrollY', 500)
            .withOption('scrollX', '100%')
            .withDOM('lftrpi')
            .withScroller();
        }           
}]);

EDIT

I simplified the code and used a resolve on the actual data attribute of result not object itself which produced data in my table.

The following modification based on my Anti-Pattern usage is the best I could get so far to achieve the same results.

angular.module('Action').controller('ActionController', ['$http', '$resource', '$scope', '$state', '$timeout', '$q', 'DTOptionsBuilder', function($http, $resource, $scope, $state, $timeout, $q, DTOptionsBuilder){
        $scope.actionitems = {};    

        function GetActionItems2()
        {
           return $resource('actionitems.json').query().$promise;
        }

        function GetActionItems() {           
            var defer = $q.defer();
            var res = $http.get('api/actionitems').then(function(result){
            var data = result.data;
            $.each(data, function(key, actionitem){
                data[key] =  [ 
                            actionitem.actionitemid, 
                            actionitem.actionitemtitle,
                            actionitem.criticality,
                            actionitem.assignor,
                            actionitem.owner,
                            actionitem.altowner,
                            actionitem.approver,
                            actionitem.assigneddate,
                            actionitem.duedate,
                            actionitem.ecd,
                            actionitem.completiondate,
                            actionitem.closeddate
                        ];    

                });
                $scope.actionitems = data;
                defer.resolve(data);
            });
            return defer.promise;
        }   

        $scope.init = function(){
            var vm = this;
            vm.dtOptions = DTOptionsBuilder
            .fromFnPromise(GetActionItems)
            .withPaginationType('full_numbers')
            //.withOption('drawCallback', reload)
            .withDisplayLength(10)
            //.withOption('order', [1, 'desc'])
            .withOption('scrollY', 500)
            .withOption('scrollX', '100%')
            .withDOM('lftrpi')
            .withScroller();
        }           
}]);
Vahe
  • 1,699
  • 3
  • 25
  • 76
  • Avoid using the [deferred Anti-pattern](https://stackoverflow.com/questions/30750207/is-this-a-deferred-antipattern). If the $http service returns a rejected promise, then the promise returned to the `DTOptionBuilder` will hang. – georgeawg Apr 18 '19 at 06:55
  • @georgeawg, seeing as this is my first attempt, I will make the necessary adjustments and post my code. Thanks for the pointer in the right direction. – Vahe Apr 18 '19 at 13:41
  • Also accessing `scope.$$phase` is a [code smell](https://en.wikipedia.org/wiki/Code_smell), a symptom of a deeper problem. – georgeawg Apr 18 '19 at 13:49
  • Please let me know if the updated approach is sound as I found this is possibly the only way to get the data into my table. – Vahe Apr 18 '19 at 15:06