簡體   English   中英

在指令中使用 ng-transclude 進行 ng-repeat

[英]ng-repeat with ng-transclude inside a directive

我想在內容更改時創建一個具有自定義行為的列表。 我嘗試為此創建一個指令,但我對如何將 ng-transclude 與 ng-repeat 指令結合起來有點迷茫。 有人可以讓我走上正軌嗎?

網址:

<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 指令中的邏輯,因為它已經做了很多我想要的。 最好我不想復制代碼。 我只想增強它的行為,而不是改變它

自己解決了這個問題:

通過在編譯模板時添加ng-repeat屬性並將我的屬性內容提供給它,我可以在編譯步驟( jsfiddle )中做到這一點。

網址:

<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);
      }        
    }
  }
});

實現此目的的替代方法如下。

索引.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>

索引.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 問題討論

不幸的是,其他答案不適用於最新版本的 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>

轉置不是必需的,因為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() 在下面引用的小提琴中,我有 $watch items數組。 $watch 回調可以訪問element ,因此您可以訪問生成的 DOM 項。 回調將element.children()記錄到控制台。

小提琴

總之,要將自定義行為添加到列表中,只需將指令放入 ul 或 ol 上即可。

我遇到了同樣的問題,最終在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 (MyList)

<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 中可以看到一個工作示例。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM