can't simply use ES6 promise to update data in ng-repeat?
Asked Answered
H

2

0

I'm trying to build a drill-down list with angular and es6 promise. Without using promise my code works as demoed in the snippet below. Every time you click the parent, it expands the children (just foo and bar in the sample 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>

However when I change it to using promise, something goes wrong: now you'd have to double click the element to expand it and the scopes are also messed up.

The difference is essentially just in the load function in the service and the directive controller. So far I haven't really looked into angular's $q api but why can't I just use promise? Is there some magic there 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>
Hurl answered 14/6, 2015 at 22:52 Comment(2)
@Sukima you're both incorrect on most of your assertions (Angular doesn't have Q built in it has $q which is different, ES6 is not "lower level" etc) and misleading.Contract
OK, ES6 Promises are a less feature full implementation that many libraries (including $q) build upon in terms of API. Since Angular comes with $q why not use it and be consistent across your code. Seems like avoiding the library's built in nomenclature is fighting against the frame work.Lexicology
C
2

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

You'd have to coerce the ES6 promise to a $q one by doing:

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

Alternatively, you can write a helper for binding it to a 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(...
});
Contract answered 14/6, 2015 at 23:3 Comment(2)
sry for accepting the answer late. This totally makes sense.Hurl
It's perfectly OK to take 2 weeks to accept an answer don't worry about it.Contract
A
0

Maybe because you are outside the angular world in the 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 works :

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 angularjs doc :

'$q is integrated with the $rootScope.Scope Scope model observation mechanism in angular, which means faster propagation of resolution or rejection into your models and avoiding unnecessary browser repaints, which would result in flickering UI.''

Adroit answered 14/6, 2015 at 23:2 Comment(2)
hmm but if $scope.$apply is required then how come it works after you double click the item? From my understanding of angular if that's required then it shouldn't work no matter how many times you click the item.Hurl
No no, don't do $scope.$apply, never do $scope.$apply, it only belongs in wiring once, writing this in every .then is a terrible idea.Contract

© 2022 - 2024 — McMap. All rights reserved.