jqueryangularjsangularjs-directiveangularjs-scopeangularjs-compile

Compile directive from dynamically inserted tag


I want to create a dashboard comprising of several smaller "apps". I have the following markup (simplified):

<div id="rowcontainer">
  <div ng-repeat="appRow in dashboard.appRows">
    <div class="row">
      <div ng-repeat="app in appRow.apps">
        {{app}}
      </div>
    </div>
  </div>
</div>

dashboard.appRows keeps my rows which consist of my "apps" (appRow.apps). When I click a button I want to dynamically add a directive by replacing the {{app}} placeholder with a directive-tag and then compiling ($compile) this very tag I just inserted. The kicker is, in the click handler, when I updated my model, the DOM is not updated (obviously) in the function and $() will fail to find the DOM node.

For clarification:

I want to make this:

<div ng-repeat="app in appRow.apps">
  {{app}}
</div>

become this:

<div ng-repeat="app in appRow.apps">
  <my-directive></my-directive>
</div>

and finally $compile the above to this:

<div ng-repeat="app in appRow.apps">
  <my-directive>CONTENTS OF MY TEMPLATE</my-directive>
</div>

Attempting this all in the same function does not work, calling $scope.$apply() throws an error ($digest already running) and I don't know if there is a callback. And finding the DOM element does not work. Thank you for your help or alternative suggestions.

Further explanation:

The directive <my-directive> could be any directive (<my-directive>, <your-directive>, ...). In my template there is a a place-holder {{app}} which I will replace at runtime with any of those directives mentioned (assume you click a button and the directive is replacing {{app}}). And then I want my directive to be working.


Solution

  • A way I can think of is using a container directive to hold the compiled directive.

    angular.module('test', [])
      .controller('Test', TestController)
      .directive('appLoader', appLoaderDirective)
      .directive('apple', appleDirective)
      .directive('banana', bananaDirective);
      
    function TestController($scope) {
      $scope.apps = ['apple', 'banana']
    }
    
    function appLoaderDirective($compile) {
      return {
        scope: { app: '=' },
        link: function(scope, element, attr) {
          // you might have to do camel case to kebab case conversion here, depending on your input
          element.append($compile("<" + scope.app + "></" + scope.app + '>')(scope));
        }
      }
    }
    
    function appleDirective() {
      return {
        template: '<div>i am apple!</div>'
      }
    }
    
    function bananaDirective() {
      return {
        template: '<div>i am banana!</div>'
      }
    }
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
    
    <div ng-app='test' ng-controller='Test'>
      <div ng-repeat='app in apps'>
        <app-loader app='app'></app-loader>
      </div>
    </div>