Ng-repeat с ng-transclude внутри директивы
Я хочу создать список с пользовательским поведением при его изменении содержимого. Я пытаюсь создать директиву для этого, но я немного теряюсь в том, как объединить 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;
});
}
}
});
У меня есть часть этого несколько работающего здесь
EDIT:
Критерии:
- Шаблон элемента должен быть определен в представлении, а не в директиве, и он должен иметь доступ к свойству item в дочерней области. В идеале я хочу определить это, как это делается в директиве ng-repeat
- Директива должна иметь доступ к списку, чтобы я мог установить правильные часы и изменить их. Если возможно, я хотел бы иметь легкий доступ к сгенерированным элементам DOM (я также могу сделать это с помощью
element[0].querySelectorAll('ul>li')
или что-то еще, он должен работать только в Chrome).
- Если возможно, я хотел бы повторно использовать логику в директиве ng-repeat, потому что она уже делает многое из того, что я хочу. Я предпочитаю не копировать код. Я просто хочу увеличить его поведение, а не изменять его.
Ответы
Ответ 1
Решил проблему самостоятельно:
Я могу сделать это на этапе компиляции (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);
}
}
}
});
Ответ 2
Альтернативный способ добиться этого следующим образом.
Index.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();
});
});
}
};
});
Смотрите в действии в этом подобном плунжере. Основываясь на этом продолжительном обсуждении проблемы Github.
Ответ 3
Другие ответы, к сожалению, не работают с новейшей версией углового (я проверил 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>
Ответ 4
Передача не требуется, поскольку items
содержит то, что нам нужно для создания шаблона. Иными словами, внутри элемента нет ничего, т.е. <mylist>nothing new here we need to transclude</mylist>
. Кажется, что Angular будет делать $watch для нас тоже.
.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>
Fiddle.
Обратите внимание, что создание новой области необязательно, поэтому вы можете прокомментировать эту строку:
//scope: true,
Обновить. Возможно, вы можете создать область выделения:
scope: { items: '='},
HTML:
<mylist items=items></mylist>
Fiddle.
Update2: на основе дополнительной информации, предоставленной Jan:
Шаблон элемента должен быть определен в представлении... Я хотел бы повторно использовать логику в директиве ng-repeat
Хорошо, поэтому давайте поместим все это в представление и используем ng-repeat:
<ul mylist>
<li ng-repeat="myItem in items">
<span class="etc">{{myItem}}</span>
</li>
</ul>
у него [директива] должен быть доступ к свойству item в дочерней области... Директива должна иметь доступ к списку, чтобы я мог установить правильные часы и изменить их.
Следуя вашей оригинальной скрипте, мы будем использовать нормальную область для дочерних элементов (т.е. дочерняя область будет прототипически наследоваться от родительской области): scope: true,
. Это гарантирует, что директива имеет доступ к свойствам, определенным в области контроллера, например, items
.
доступ к сгенерированным элементам DOM
Функция директивной ссылки имеет аргумент element
. Поэтому в HTML выше элемент будет установлен в элемент <ul>
. Таким образом, у нас есть доступ ко всем элементам DOM. Например, element.find('li')
или element.children()
. В приведенной ниже ссылке, я имею $watch массив items
. Обратный вызов $watch имеет доступ к element
, поэтому у вас есть доступ к сгенерированным элементам DOM. Журналы обратного вызова element.children()
на консоли.
Fiddle.
Таким образом, чтобы добавить пользовательское поведение в список, просто нарисуйте директиву на ul или ol и уходите.