2

I have a custom attribute directive (i.e., restrict: "A") and I want to pass two expressions (using {{...}}) into the directive as attributes. I want to pass these attributes into the directive's template, which I use to render two nested div tags -- the outer one containing ng-controller and the inner containing ng-include. The ng-controller will define the controller exclusively used for the template, and the ng-include will render the template's HTML.

An example showing the relevant snippets is below.

HTML:

<div ng-controller="appController">
    <custom-directive ctrl="templateController" tmpl="template.html"></custom-directive>
</div>

JS:

function appController($scope) {
    // Main application controller
}

function templateController($scope) {
    // Controller (separate from main controller) for exclusive use with template
}

app.directive('customDirective', function() {
    return {
        restrict: 'A',
        scope: {
            ctrl: '@',
            tmpl: '@'
        },
        // This will work, but not what I want
        // Assigning controller explicitly
        template: '<div ng-controller="templateController">\
                       <div ng-include="tmpl"></div>\
                   </div>'
        // This is what I want, but won't work
        // Assigning controller via isolate scope variable from attribute
        /*template: '<div ng-controller="ctrl">\
                         <div ng-include="tmpl"></div>\
                     </div>'*/
    };
});

It appears that explicitly assigning the controller works. However, I want to assign the controller via an isolate scope variable that I obtain from an attribute located inside my custom directive in the HTML.

I've fleshed out the above example a little more in the Plunker below, which names the relevant directive contentDisplay (instead of customDirective from above). Let me know in the comments if this example needs more commented clarification:

Plunker

Using an explicit controller assignment (uncommented template code), I achieve the desired functionality. However, when trying to assign the controller via an isolate scope variable (commented template code), it no longer works, throwing an error saying 'ctrl' is not a function, got string.

The reason why I want to vary the controller (instead of just throwing all the controllers into one "master controller" as I've done in the Plunker) is because I want to make my code more organized to maintain readability.

The following ideas may be relevant:

  • Placing the ng-controller tags inside the template instead of wrapping it around ng-include.
  • Using one-way binding ('&') to execute functions instead of text binding ('@').
  • Using a link function instead of / in addition to an isolate scope.
  • Using an element/class directive instead of attribute directive.
  • The priority level of ng-controller is lower than that of ng-include.
  • The order in which the directives are compiled / instantiated may not be correct.

While I'm looking for direct solutions to this issue, I'm also willing to accept workarounds that accomplish the same functionality and are relatively simple.

Community
  • 1
  • 1
bunting
  • 687
  • 1
  • 6
  • 11

1 Answers1

1

I don't think you can dynamically write a template key using scope, but you certainly do so within the link function. You can imitate that quite succinctly with a series of built-in Angular functions: $http, $controller, $compile, $templateCache.

Plunker

Relevant code:

    link: function( scope, element, attrs )
    {
      $http.get( scope.tmpl, { cache: $templateCache } )
        .then( function( response ) {
          templateScope = scope.$new();
          templateCtrl = $controller( scope.ctrl, { $scope: templateScope } );
          element.html( response.data );
          element.children().data('$ngControllerController', templateCtrl);
          $compile( element.contents() )( templateScope );
        });
    }

Inspired strongly by this similar answer.

Community
  • 1
  • 1
Morgan Delaney
  • 2,349
  • 3
  • 20
  • 23
  • Thanks for the workaround! A follow-up question: You mention that "`template` may not have enough access to `scope`" -- how can a component have only _partial_ access to `scope`? Shouldn't it be all or nothing? In words more specific to my example, how can `tmpl` be accessible by the `template` key, but not `ctrl`? – bunting Jun 24 '14 at 20:22
  • Sorry that was a bit unclear, I just mean I don't know of a way to make a string such as `'
    '`. (Note the reference to `scope`; in a normal `template` string you would let Angular process each scope property internally.
    – Morgan Delaney Jun 24 '14 at 21:32