不能简单地使用 ES6 promise 在 ng-repeat 中更新数据吗?

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

我正在尝试使用 angular 和 es6 promise 构建一个向下钻取列表。在不使用 promise 的情况下,我的代码可以像下面的代码片段中演示的那样工作。每次单击父项时,它都会展开子项(为简单起见,示例中只有 foo 和 bar)。

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>

然而,当我将其更改为使用 promise 时,出现了问题:现在您必须双击该元素才能将其展开,并且范围也被弄乱了。

主要区别在于服务和指令控制器中的 load 函数。到目前为止,我还没有真正研究过 angular 的 $q api 但为什么我不能只使用 promise? $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>

可能是因为你在回调函数的angular世界之外

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

这不是最好的解决方案,但如果您调用 $scope.$apply() 它应该有效:

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

jsfiddle 与 $scope.$apply() :)

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

最好的解决方案是使用 $q。

来自 angularjs 文档:

'$q 与 angular 中的 $rootScope.Scope Scope 模型观察机制集成,这意味着可以更快地将分辨率或拒绝传播到您的模型中,并避免不必要的浏览器重绘,这会导致闪烁 UI.''

这将要求 ES6 承诺公开一个用于设置调度程序的钩子(如 bluebird 承诺)或公开 "post-then" 钩子——这两者都不是公开的。

您必须通过以下方式将 ES6 承诺强制为 $q

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

或者,您可以编写一个帮助程序将其绑定到范围:

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

然后做:

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