46

我想在内容更改时创建一个具有自定义行为的列表。我尝试为此创建一个指令,但我对如何将 ng-transclude 与 ng-repeat 指令结合起来有点迷茫。有人可以让我走上正轨吗?

html:

<div ng-app="myApp">
  <div ng-controller="ctrl">
    <mylist items="myItem in items">
       <span class="etc">{{myItem}}</span>
    </mylist>
  </div>
</div>

Javascript:

angular.module('myApp', [])    

.controller('ctrl', function ($scope) {
  $scope.items = ['one', 'two', 'three'];
})    

.directive('mylist', function () {
  return {
    restrict:'E',
    transclude: 'element',
    replace: true,
    scope: true,
    template: [
      '<ul>',
        '<li ng-repeat="WhatGoesHere in items" ng-transclude></li>',
      '</ul>'
    ].join(''),
    link: function (scope, element, attr) {
      var parts = attr.items.split(' in ');
      var itemPart = parts[0];
      var itemsPart = parts[1];
      scope.$watch(itemsPart, function (value) {
        scope.items = value; 
      });      
    }
  }
});

我有一部分在这里工作

编辑:

标准:

  • 项目的模板必须在视图中定义,而不是在指令中,并且它必须能够访问子范围内的项目属性。理想情况下,我想像在 ng-repeat 指令中那样定义它
  • 该指令必须有权访问该列表,以便我可以设置适当的手表并进行更改。如果可能的话,我希望能够轻松访问生成的 DOM 项目(我也可以使用它element[0].querySelectorAll('ul>li')或其他东西来实现,它只需要在 Chrome 上工作)。
  • 如果可能的话,我想重用 ng-repeat 指令中的逻辑,因为它已经做了很多我想做的事情。最好我不想复制代码。我只想增强它的行为,而不是改变它
4

5 回答 5

20

自己解决了这个问题:

我可以在编译步骤(jsfiddle)中通过ng-repeat在编译模板时添加属性并将我的属性内容提供给它来完成。

html:

<div ng-app="myApp">
  <div ng-controller="ctrl">
    <mylist element="myItem in items">{{myItem}}</mylist>
  </div>
</div>

Javascript:

var myApp = angular.module('myApp', [])

.controller('ctrl', function ($scope) {
  $scope.items = ['one', 'two', 'three'];
})

.directive('mylist', function ($parse) {
  return {
    restrict:'E',
    transclude: 'element',
    replace: true,
    scope: true,
    template: [
      '<ul>',
      '<li ng-transclude></li>',
      '</ul>'
    ].join(''),
    compile: function (tElement, tAttrs, transclude) {
      var rpt = document.createAttribute('ng-repeat');
      rpt.nodeValue = tAttrs.element;
      tElement[0].children[0].attributes.setNamedItem(rpt);
      return function (scope, element, attr) {
        var rhs = attr.element.split(' in ')[1];
        scope.items = $parse(rhs)(scope);
        console.log(scope.items);
      }        
    }
  }
});
于 2013-01-20T16:26:37.593 回答
14

实现此目的的另一种方法如下。

索引.html:

<html ng-app='myApp'>

<head>
    <title>AngularJS Transclude within Repeat Within Directive</title>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js"></script>
    <script src='index.js'></script>
</head>

<body ng-controller='myController'>
    <people>Hello {{person.name}}</people>
    <button name="button" ng-click="changeRob()">Change Rob</button>
</body>
</html>

index.js:

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

myApp.controller( 'myController', function( $scope ) {
    $scope.people = [
        { name: 'Rob'  },
        { name: 'Alex' },
        { name: 'John' }
    ];

    $scope.changeRob = function() {
        $scope.people[0].name = 'Lowe';
    }
});

myApp.directive( 'people', function() {
    return {
        restrict: 'E',

        transclude: true,
        template: '<div ng-repeat="person in people" transcope></div>',
    }
});

myApp.directive( 'transcope', function() {
    return {
        link: function( $scope, $element, $attrs, controller, $transclude ) {
            if ( !$transclude ) {
                throw minErr( 'ngTransclude' )( 'orphan',
                    'Illegal use of ngTransclude directive in the template! ' +
                    'No parent directive that requires a transclusion found. ' +
                    'Element: {0}',
                    startingTag( $element ));
            }
            var innerScope = $scope.$new();

            $transclude( innerScope, function( clone ) {
                $element.empty();
                $element.append( clone );
                $element.on( '$destroy', function() {
                    innerScope.$destroy();
                });
            });
        }
    };
}); 

在这个类似的 plunker中查看它的实际效果。基于这个长期的 Github 问题讨论

于 2014-07-01T14:12:01.457 回答
9

不幸的是,其他答案不适用于最新版本的 angular(我检查过1.4),所以我认为分享我发现的这个 jsbin是有好处的:

var app = angular.module('app', [])
  .controller('TestCtrl', function($scope) {
    $scope.myRecords = ['foo', 'bar', 'baz'];
  });

app.directive('myDirective', function($compile) {
  var template = '<div id="inner-transclude" ng-repeat="record in records"></div>';

  return {
    scope: {
      records: '='
    },
    restrict: 'A',
    compile: function(ele) {
      var transclude = ele.html();
      ele.html('');

      return function(scope, elem) {
        var tpl = angular.element(template);
        tpl.append(transclude);

        $compile(tpl)(scope);

        elem.append(tpl);
      };
    }
  };
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.js"></script>


<div ng-app="app" ng-controller="TestCtrl">
  <div my-directive records="myRecords">
    ?: {{record}}
  </div>

</div>

于 2016-06-10T20:10:00.270 回答
5

嵌入不是必需的,因为items包含我们需要渲染模板的内容。换句话说,元素内部没有任何东西——即 <mylist>nothing new here we need to transclude</mylist>. 看来 Angular 也会为我们做 $watching。

.directive('mylist', function () {
  return {
    restrict:'E',
    replace: true,
    scope: true,
    template: [
      '<ul>',
      '<li ng-repeat="myItem in items">{{myItem}}</li>',
      '</ul>'
    ].join('')
  }
});

HTML:

<mylist></mylist>

小提琴

请注意,创建新范围是可选的,因此您可以注释掉这一行:

//scope: true,

更新:您可以选择创建一个隔离范围:

scope: { items: '='},

HTML:

<mylist items=items></mylist>

小提琴

更新2:基于 Jan 提供的附加信息:

项目的模板必须在视图中定义...我想重用ng-repeat指令中的逻辑

好的,让我们把它全部放在视图中,并使用 ng-repeat:

<ul mylist>
  <li ng-repeat="myItem in items">
    <span class="etc">{{myItem}}</span>
   </li>
</ul>

它 [指令] 必须有权访问子范围内的项目属性...该指令必须有权访问列表,以便我可以设置适当的监视和更改内容

按照你原来的小提琴,我们将使用一个普通的子范围(即,子范围将原型继承自父范围)scope: true,:这将确保指令可以访问在控制器范围内定义的属性,例如items.

访问生成的 DOM 项

该指令的链接函数有一个element参数。所以在上面的 HTML 中, element 将被设置为<ul>元素。所以我们可以访问所有的 DOM 元素。例如,element.find('li')element.children()。在下面引用的小提琴中,我有 $watchitems数组。$watch 回调可以访问element,因此您可以访问生成的 DOM 项。回调记录element.children()到控制台。

小提琴

总之,要将自定义行为添加到列表中,只需将指令放在 ul 或 ol 上,然后就可以了。

于 2013-01-17T21:45:49.693 回答
0

我遇到了同样的问题并最终添加了一些代码,ng-transclude以便它允许观察和接受来自使用父级的自定义数据。

  • 这样,嵌入的数据就可以访问祖父范围
  • 并且还可以访问给它的 ng-repeat 数据。

用法

祖父母.js

<div>{{ $ctrl.listName }}</div.

<my-list items="$ctrl.movies">
   <div>From context: {{ name }}</div>
   <div>From grandparent: {{ $ctrl.listName }}</div>
</my-list>

Parent.js(我的列表)

<li ng-repeat="item in $ctrl.items track by item.id">
  <cr-transclude context="item"></cr-transclude>
</li>

ng-transclude 的代码更改

return function ngTranscludePostLink(
   ...
  ) {
  let context = null;
  let childScope = null;
  ...
  $scope.$watch($attrs.context, (newVal, oldVal) => {
    context = newVal;
    updateScope(childScope, context);
  });
  ...
  $transclude(ngTranscludeCloneAttachFn, null, slotName);
  ...
  function ngTranscludeCloneAttachFn(clone, transcludedScope) {
     ...                                 
     $element.append(clone);
     childScope = transcludedScope;
     updateScope(childScope, context);
     ...
  }
  ...
  function updateScope(scope, varsHash) {
    if (!scope || !varsHash) {
      return;
    }
    angular.extend(scope, varsHash);
  }
}

完整代码在Github

在Codesandbox中可以看到一个工作示例。

于 2020-10-12T06:16:17.363 回答