ng-repeat with ng-transclude inside a directive

Transcluding isn't necessary because items contains what we need to render the template. Put another way, there isn't anything inside the element -- i.e., <mylist>nothing new here we need to transclude</mylist>. It seems Angular will do the $watching for us too.

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

Note that creating a new scope is optional, so you could comment out this line:

//scope: true,

Update: You could optionally create an isolate scope:

scope: { items: '='},

HTML:

<mylist items=items></mylist>

Fiddle.

Update2: based on additional info provided by Jan:

The template of the item must be defined in the view... I would like to reuse the logic in the ng-repeat directive

Okay, so lets put it all in the view, and use ng-repeat:

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

it [the directive] must have access to an item property in a child scope... The directive must have access to the list so I can set proper watches and change things

Following your original fiddle, we'll use a normal child scope (i.e., the child scope will prototypically inherit from the parent scope): scope: true,. This will ensure the directive has access to the properties defined on the controller's scope, e.g., items.

access to the generated DOM items

The directive's link function has an element argument. So in the HTML above, element will be set to the <ul> element. So we have access to all the DOM elements. E.g., element.find('li') or element.children(). In the fiddle referenced below, I have it $watch the items array. The $watch callback has access to element, so you have access to the generated DOM items. The callback logs element.children() to the console.

Fiddle.

In summary, to add custom behavior to a list, just plop a directive onto a ul or ol and away you go.


Solved the problem myself:

I am able to do it in the compile step (jsfiddle) by adding the ng-repeat attribute when the template is compiled and feeding it the content of my attribute.

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

An alternative way to achieve this as follows.

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

See it in action in this similar plunker. Based on this long Github issue discussion.


Other answers unfortunately does not work with newest version of angular(I checked 1.4) so I think there is a benefit to share this jsbin I found:

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>