airnan airnan - 6 months ago 23
AngularJS Question

Include different angular directives in ng-repeat

I have several angular directives.

I want to include them in an accordion directive with an ng-repeat depending on the type.

I want it to be reusable, so I don't want the accordion to have knowledge of the type of directives it is rendering.

That's why I don't want to use an ng-switch directive inside the accordion.
Here is a very simplified demo. In reality those directives will have their own attributes for angular to compile.



var testApp = angular.module('testApp', []);
(function() {
function Element1() {
return {
template: '<span>hello</span>',
restrict: 'E',
replace: true
}
}
testApp.directive('elementOne', Element1);
}());

(function() {
function Element2() {
return {
template: '<span>world</span>',
restrict: 'E',
replace: true
}
}
testApp.directive('elementTwo', Element2);
}());

(function() {
function Accordion() {
return {
template: '<ul><li ng-repeat="element in elements"><button>Toggle</button> Here should be the directive</li></ul>',
restrict: 'E',
replace: true,
controller: function($scope) {
$scope.elements = [{
type: 1
}, {
type: 2
}];
}
}
}
testApp.directive('elementAccordion', Accordion);
}());

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="testApp">
<element-accordion></element-accordion>
</div>





Thanks!

Answer

I've built something very similar a few weeks ago. I wanted to render a list of directives and select the type of directive based on the type of an element in an array (just like you). I tried to avoid ngSwitch because I've really many directives and want to add new directives without changing the ngSwitch statement every time. Let me show a simplified version of what I did. I hope this will help you to build your own version.

First, we need a global directory of element diretives and a helper function to register new directives. In my case, the helper function even creates the directive, but I'll skip this to keep it simple:

angular.module("app").provider('elementDirectory', function() {        
  return {
    elements: [],
    $get: function () {
      return {
        elements: this.elements
      }
    }
  };
});

function registerElementDirective(type, directiveName) {
  angular.module("app").config(['elementDirectoryProvider', function (elementDirectoryProvider) {
    elementDirectoryProvider.elements.push({
      type : type,
      directive : directiveName
    });
  }]);
}

Now, we can create some directives like this:

angular.module("app").directive('elementA', function() {
    return {
      template: '<span>ElementA</span>',
    }
});
registerElementDirective('A', 'element-a');

The most interesting part is a directive I called elementSwitch. It takes an array of elements and dynamically adds an angular.element for each element. Therefore, it creates prototypes for each element in the elementDirectoy and uses the clone() method on changes. (I think we could skip this part, it was an optimization).

angular.module('app').directive('elementSwitch', elementSwitch);    
elementSwitch.$inject = ['$compile', 'elementDirectory'];
function elementSwitch($compile, elementDirectory) {

  var prototypes = {};
  elementDirectory.elements.forEach(function (e) {
    var directiveElem = angular.element('<' + e.directive + '>');
    prototypes[e.type] = directiveElem;
  });

  return {
    scope: {
      elements: '=elementSwitch'
    },
    link: function (scope, elem, attr) {

      var childScopes = [];
      scope.$watchCollection('elements', function (newValue, oldValue) {
        childScopes.forEach(function (s) {
          s.$destroy();
        });
        childScopes = [];
        elem.empty();

        newValue.forEach(function (element, index) {
          var childScope = scope.$new();
          childScopes.push(childScope);
          var directiveElem = prototypes[element].clone();
          directiveElem.attr('element', '_elements[' + index + ']');
          $compile(directiveElem)(childScope);
          elem.append(directiveElem);
        });
        scope._elements = newValue;
      });
    }
  }
}

Here is a complete example: https://codepen.io/hansmaad/pen/oLvRmj

It doesn't do the same thing you want, but you should get an idea how you could achieve your goal.

Comments