1

I'm trying to dynamically add directive names to my directive from a json object. Angular however is only interpolating the directive name which is pulled from a JSON tree once, Angular is then not recognizing and compiling the dynamic children directives once the name is interpolated.

I have tried adding the interpolate service to my DDO so that I can manually interpolate the JSON values, and then have Angular compile.

I however get undefined for $interpolate(tAttrs.$attr.layout) I'm passing the json object to my isolated scope as layout, when I try to access the attr layout I get undefined. My question is how can I access layout object values in the pre link or before compile so that I can interpolate the values and inject them in.

Or do I need to have angular recompile as described here: How do I pass multiple attributes into an Angular.js attribute directive?

Any help would be great.

{
  "containers": [
  {
    "fluid": true,
    "rows": [
    {
      "columns": [
        {
          "class": "col-md-12",
          "directive": "blog"
        }
      ]
    },
    {
      "columns": [
        {
          "class": "col-md-6 col-md-offset-3 col-xs-10 col-xs-offset-1",
          "directive": "tire-finder"
        }
      ]
    }
  ]
 }
]
}

...

<div layout="layout" ng-repeat="container in layout.containers" ng-class="container">
  <div ng-repeat="row in container.rows">
    <div ng-repeat="column in row.columns" ng-class="column.class">
        <{{column.directive}}></{{column.directive}}>
    </div>
  </div>
</div>

...

angular.module('rpmsol').directive('wpMain', wpMainDirective);

function wpMainDirective($interpolate) {

    var controller =  function(brainService, $scope, $state) {
        $scope.directive = {};
        var currentState = $state.current.name;

        brainService.getDirectiveScope('wpMain', {}).then(function(response) {
            $scope.layout = response.states[currentState];
        });
    };

    var compile = function(tElement, tAttrs, transclude) {
        var directiveNames = $interpolate(tAttrs.$attr.layout);
    }  

    return {

        restrict: 'E',
        // replace: true,
        scope: {
          layout: '=',
        },
        controller: controller,
        templateUrl: 'directive/wpMain/wpMain.html',
        compile: compile
    };
};
Community
  • 1
  • 1
ArturoRomero
  • 320
  • 1
  • 3
  • 10

2 Answers2

1

If you're only dealing with a couple options for what a column might be, I would suggest going with @georgeawg's answer.

However, if you expect that number to grow, what you might opt for instead is something along the following lines:

<div layout="layout" ng-repeat="container in layout.containers" ng-class="container">
  <div ng-repeat="row in container.rows">
  <div ng-repeat="column in row.columns" ng-class="column.class">
    <column-directive type="column.directive"></column-directive>
  </div>
</div>

and then in your JS...

yourApp.directive('columnDirective', columnDirectiveFactory);

columnDirectiveFactory.$inject = ['$compile'];
function columnDirectiveFactory ($compile) {
  return {
    restrict: 'E',
    scope: {
      type: '='
    },
    link: function (scope, elem, attrs) {
      var newContents = $compile('<' + scope.type + '></' + scope.type + '>')(scope);
      elem.contents(newContents);
    }
  };
}

To the best of my knowledge, Angular doesn't have any built-in facility to choose directives in a truly dynamic fashion. The solution above allows you to pass information about which directive you want into a generic columnDirective, whose link function then goes about the business of constructing the correct element, compiling it against the current scope, and inserting into the DOM.

cmw
  • 855
  • 7
  • 17
  • `elem.contents(newContents)` should be `elem.replaceWith(newContents)` – georgeawg Nov 28 '15 at 07:18
  • Thank you for your answer. I expect my my Directives to grow based on the JSON data. The idea is to make a Data Driven app that will make websites for multiple clients. Think Calypso https://developer.wordpress.com/calypso/ built with Angular. I will try out your recommendation – ArturoRomero Nov 28 '15 at 14:15
0

There was an issue with the promise in my original posted code which was preventing me from recompiling the template with the correct directive names. The issue was that I was trying to access the JSON object in the preLink function, but the promise hadn't been resolved yet. This meant that my scope property didn't yet have data.

To fix this I added my service promise to the directive scope $scope.layoutPromise = brainService.getDirectiveScope('wpMain', {}); to which I then called and resolved in my link function. I managed to have Angular compile all of my directive names from the JSON object, but I had to do it in a very hackish way. I will be taking your recommendations @cmw in order to make my code simpler and more 'Angulary'

This is currently my working code:

...

    angular.module('rpmsol').directive('wpMain', wpMainDirective);

function wpMainDirective($interpolate, $compile) {

    var controller =  function(brainService, $scope, $state) {
        $scope.currentState = $state.current.name;
        $scope.layoutPromise = brainService.getDirectiveScope('wpMain', {});
    };

    var link = function(scope, element, attributes) {
        scope.layoutPromise.then(function sucess(response) {
            var template = [];
            angular.forEach(response.states[scope.currentState].containers, function(container, containerKey) {
                template.push('<div class="container' + (container.fluid?'-fluid':'') + '">');
                //loop rows
                angular.forEach(container.rows, function(row, rowkey) {
                    template.push('<div class="row">');
                        angular.forEach(row.columns, function(column, columnKey) {
                            template.push('<div class="' + column.class + '">');
                            template.push('<' + column.directive +'></' + column.directive + '>')
                            template.push('</div>');
                        });
                    template.push('</div>');
                });
                template.push('</div>');
            });
            template = template.join('');
            element.append($compile(template)(scope));
        })
    };

    return {
        scope: true,
        controller: controller,
        link: link
    };
};
ArturoRomero
  • 320
  • 1
  • 3
  • 10