2

我有一个要在 Angular.js 中表示的递归数据结构。此处提供了一个简化的演示:

http://plnkr.co/edit/vsUHLYMfI4okbiVlCK7O?p=preview

在预览中,我有一个递归对象的以下 HTML:

<ul>
  <li ng-repeat="person in people">
    <span ng-click="updateClicks(person)">{{person.name}}</span>
    <ul>
      <li ng-repeat="kid in person.kids">
        <span ng-click="updateClicks(kid)">{{kid.name}}</span>
      </li>
    </ul>
  </li>
</ul>

在我的应用程序中,视图要复杂得多。我想有一种方法以递归方式为每个人生成模板 html。我尝试使用指令执行此操作,但是当我没有隔离范围时遇到了无限循环的问题。当我确实隔离了范围时,我不再能够调用绑定到控制器的函数(在这个例子中是updateClicks函数,但在我的应用程序中有几个)。

如何递归地为这些对象生成 html,并且仍然能够调用属于控制器的函数?

4

2 回答 2

3

我认为最好的方法是使用 $emit。

假设您的递归指令如下所示:

directive('person', function($compile){
  return{
  restrict: 'A',
  link: function(scope, element, attributes){
    //recursive bit, if we've got kids, compile & append them on
    if(scope.person.kids && angular.isArray(scope.person.kids)) {
     $compile('<ul><li ng-repeat="kid in person.kids" person="kid"></li></ul>')(scope, function(cloned, scope){
       element.find('li').append(cloned); 
     });
    }
  },
  scope:{
    person:'='
  },
  template: '<li><span ng-click="$emit(\'clicked\', person)">{{person.name}}</span></li>'
  }
});

注意ng-click="$emit(clicked, person)"代码,不要分心\,那只是为了逃避。$scope.$emit 将在你的作用域链上发送一个事件,这样在你的控制器中,你点击的函数大部分保持不变,但现在你正在监听事件而不是被 ng-click 触发。

$scope.$on('clicked', function(event, person){
  person.clicks++;
  alert(person.name + ' has ' + person.clicks + ' clicks!');
});

很酷的是,事件对象甚至具有与您的递归指令隔离的范围。

这是完全工作的 plnkr:http ://plnkr.co/edit/3z8OXOeB5FhWp9XAW58G?p=preview 甚至下降到第三级以确保递归正常工作。

于 2013-08-07T03:28:00.537 回答
2

带有角度指令的递归树没有范围隔离,强制您通过使用每个深度级别的不同范围属性来模拟隔离。

我没有找到,所以我自己写了。

假设您的 HTML 是:

<body ng-app="App" ng-controller="AppCtrl">
  <div test="tree.children" test-label="tree.label">{{b}}</div>
</body>

然后你有一个主模块和一个控制器,将一棵树添加到范围:

var App = angular.module('App', []);

App.controller('AppCtrl', function($scope, $timeout) {
  // prodive a simple tree
  $scope.tree = {
    label: 'A',
    children: [
      {
        label: 'a',
        children: [
          { label: '1' },
          { label: '2' }
          ]
      },
      {
        label: 'b',
        children: [
          { label: '1' },
          { label: '2' }
          ]
      }
      ]
  };

  // test that pushing a child in the tree is ok
  $timeout(function() {
    $scope.tree.children[1].children.push({label: 'c'});
  },2000);
  $timeout(function() {
  // test that changing a label is ok
    $scope.tree.children[1].label = 'newLabel';
  },4000);

});

最后考虑该指令的以下实现test

App.directive('test', function($compile) {
  // use an int to suffix scope properties 
  // so that inheritance does not cause infinite loops anymore
  var inc = 0;
  return {
    restrict: 'A',
    compile: function(element, attr) {
      // prepare property names
      var prop = 'test'+(++inc),
          childrenProp = 'children_'+prop,
          labelProp = 'label'+prop,
          childProp = 'child_'+prop;

      return function(scope, element, attr) {
        // create a child scope
        var childScope = scope.$new();
        function observeParams() {
          // eval attributes in current scope
          // and generate html depending on the type
          var iTest = scope.$eval(attr.test),
              iLabel = scope.$eval(attr.testLabel),
              html = typeof iTest === 'object' ?
              '<div>{{'+labelProp+'}}<ul><li ng-repeat="'+childProp+' in '+childrenProp+'"><div test="'+childProp+'.children" test-label="'+childProp+'.label">{{'+childProp+'}}</div></li></ul></div>'
            : '<div>{{'+labelProp+'}}</div>';

          // set scope values and references
          childScope[childrenProp]= iTest;
          childScope[labelProp]= iLabel;

          // fill html
          element.html(html);

          // compile the new content againts child scope
          $compile(element.contents())(childScope);
        }

        // set watchers
        scope.$watch(attr.test, observeParams);
        scope.$watch(attr.testLabel, observeParams);
      };
    }
  };
});

所有的解释都在评论中。

你可以看看JSBin

我的实现当然可以改进。

于 2013-06-13T13:25:13.587 回答