Can't just use ES6 promise to update data in ng-repeat?

I am trying to create unrolled list with angular and es6 promise. Without using promises, my code works like the demo snapshot below. Every time you click on the parent, it expands the children (just for foo and bar in the example for simplicity).

angular.module('demo', [])
  .controller('DemoController', ['$scope', 'dataService', function($scope, dataSvc) {
    $scope.entities = dataSvc.loadInitialData();
  }])
  .directive('drillDown', ['$compile', 'dataService', function($compile, dataSvc) {
    return {
      restrict: 'A',
      scope: {
        entities: '='
      },
      controller: function($scope) {
        $scope.load = function() {
          this.entity.subEntities = dataSvc.load();
        };
      },
      compile: function(element) {
        var contents = element.contents().remove();
        var compiled = null;

        return function(scope, element) {
          if (!compiled) {
            compiled = $compile(contents);
          }

          compiled(scope, function(clone) {
            element.append(clone);
          });
        };
      },
      template:
        '<li ng-repeat="entity in entities">' +
          '<a href="#" ng-click="load()"><span ng-bind="entity.name"></span></a>' +
          '<ul drill-down entities="entity.subEntities"></ul>' +
        '</li>'
    };
  }])
  .service('dataService', function() {
    this.loadInitialData = function() {
      return [
        {
          name: 'foo',
          subEntities: []
        },
        {
          name: 'bar',
          subEntities: []
        }
      ];
    };
    this.load = function() {
      return this.loadInitialData();
    };
  });
      

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.0/angular.min.js"></script>
<div ng-app="demo" ng-controller="DemoController">
  <ul drill-down entities="entities"></ul>
</div>
      

Run code


However, when I change it to use a promise, something goes wrong: now you have to double-click the item to expand it and the scopes are messed up as well.

The difference essentially comes down to just the function load

in the service and the directive controller. So far, I haven't really considered angular $q

api, but why can't I just use a promise? Is there some kind of magic in $q

?

angular.module('demo', [])
  .controller('DemoController', ['$scope', 'dataService', function($scope, dataSvc) {
    $scope.entities = dataSvc.loadInitialData();
  }])
  .directive('drillDown', ['$compile', 'dataService', function($compile, dataSvc) {
    return {
      restrict: 'A',
      scope: {
        entities: '='
      },
      controller: function($scope) {
        $scope.load = function() {
          var s = this;
          dataSvc.load().then(function(res) {
            s.entity.subEntities = res;
          });
        };
      },
      compile: function(element) {
        var contents = element.contents().remove();
        var compiled = null;

        return function(scope, element) {
          if (!compiled) {
            compiled = $compile(contents);
          }

          compiled(scope, function(clone) {
            element.append(clone);
          });
        };
      },
      template:
        '<li ng-repeat="entity in entities">' +
          '<a href="#" ng-click="load()"><span ng-bind="entity.name"></span></a>' +
          '<ul drill-down entities="entity.subEntities"></ul>' +
        '</li>'
    };
  }])
  .service('dataService', function() {
    this.loadInitialData = function() {
      return [
          {
            name: 'foo',
            subEntities: []
          },
          {
            name: 'bar',
            subEntities: []
          }
        ];
    };
    this.load = function() {
      return new Promise(function(resolve, reject) {
        resolve([
          {
            name: 'foo',
            subEntities: []
          },
          {
            name: 'bar',
            subEntities: []
          }
        ]);
      });
    };
  });
      

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.0/angular.min.js"></script>
<div ng-app="demo" ng-controller="DemoController">
  <ul drill-down entities="entities"></ul>
</div>
      

Run code


0


source to share


2 answers


This would require ES6 promises to either expose a hook to set the scheduler (like bluebird promises) or expose "post-then" hooks - none of which it does publicly.

You would need to make ES6 promise $q

by doing:

$q.when(dataSvc.load()).then(...

      

Alternatively, you can write a helper to bind it to the scope:



var withDigest = function(fn){
    fn().then(function(){
        $rootScope.evalAsync(angular.noop); // schedule digest if not scheduled
    });
};

      

And then do:

withDigest(function(){
    return dataSvc.load().then(...
});

      

+2


source


Perhaps because you are outside the angular world in a callback function

dataSvc.load().then(function(res) {
  s.entity.subEntities = res;
});

      

This is not the best solution, but if you call $scope.$apply()

it should work:

dataSvc.load().then(function(res) {
  s.entity.subEntities = res;
  $scope.$apply()
});

      

jsfiddle with $ scope. $ apply () :)



http://jsfiddle.net/Fieldset/xdxprzgw/

The best solution is to use $ q.

From the angular vector doc:

'$ q is integrated with the $ rootScope.Scope Scope model observing mechanism in angular, which means faster resolution or rejection propagation in your models and avoids unnecessary browser transitions that will flicker the UI.' '

0


source







All Articles