Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Repeating over a transclusion in Angular - best practice to access current item?

I have written the following directive:

transclude: true,
scope: { items: '=' }
...
<div class="row" ng-repeat="item in items">
  <div class="col-xs-9">
    <ng-transclude></ng-transclude>
  </div>
</div>

Is it legal / good practice to do the following when using this directive?

cakes = [ { name: 'blueberry cheesecake', color: 'blue' }, { name: 'rocky road', color: 'mostly brown' } ]
...
<custom-list items="cakes">
  <h5>{{$parent.item.name}}</h5>
</custom-list>

I am specifically talking about the $parent. aspect.

like image 810
Lawrence Wagerfield Avatar asked Oct 19 '22 17:10

Lawrence Wagerfield


2 Answers

Angular have recognised that a more flexible ng-transclude would be beneficial:

https://github.com/angular/angular.js/issues/5489

One of the suggested workarounds is to define your own override for ng-transclude that allows you to do the following:

<div ng-transclude="sibling"></div> <!-- Original behaviour -->
<div ng-transclude="parent"></div> <!-- Takes from where transclusion happens -->
<div ng-transclude="child"></div> <!-- Takes from where transclusion happens, but creates a new child scope -->

Source for custom ng-transclude:

.config(function($provide){
    $provide.decorator('ngTranscludeDirective', ['$delegate', function($delegate) {
        // Remove the original directive
        $delegate.shift();
        return $delegate;
    }]);
})

.directive( 'ngTransclude', function() {
  return {
    restrict: 'EAC',
    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 iScopeType = $attrs['ngTransclude'] || 'sibling';

      switch ( iScopeType ) {
        case 'sibling':
          $transclude( function( clone ) {
            $element.empty();
            $element.append( clone );
          });
          break;
        case 'parent':
          $transclude( $scope, function( clone ) {
            $element.empty();
            $element.append( clone );
          });
          break;
        case 'child':
          var iChildScope = $scope.$new();
          $transclude( iChildScope, function( clone ) {
            $element.empty();
            $element.append( clone );
            $element.on( '$destroy', function() {
              iChildScope.$destroy();
            });            
          });
          break;
      }
    }
  }
})
like image 182
Lawrence Wagerfield Avatar answered Oct 22 '22 23:10

Lawrence Wagerfield


If I was to generalize the problem you are trying to solve, I would say that you are trying to create a customList directive that allows the user to specify the template for each item.

transclude doesn't seem like it was meant for this - it was meant to transclude the content from the outside scope - not to backward-access the inner scope of the directive.

So, conceptually, you could do something like the following:

.directive("customList", function() {

  return {
    scope: {
      items: "="
    },
    templateUrl: function(element){
      element.data("customListTemplate", element.find("item-template"));
      return "customList.html";
    },
    compile: function(tElement, tAttrs) {
      var template = element.data("customListTemplate");
      tElement.find("item-placeholder").replaceWith(template.contents());
    }
  };
});

customList.html is:

<div ng-repeat="item in items">
  <item-placeholder></item-placeholder>
</div>

And the usage is:

<custom-list items="cakes">
  <item-template>
    {{$index}} | {{item.name}}
    <hr>
  </item-template>
</custom-list>

plunker

like image 30
New Dev Avatar answered Oct 22 '22 21:10

New Dev