3

I’m building a directive, I’m calling ‘requires-authorization’ to wrap an ng-if directive. I’d like to use it as follows:

<requires-authorization role='SuperUser'>
<!— super secret user stuff goes here, within
   the scope of this view's controller —>
</requires-authorization>

I’ve gotten as far as:

angular.module('myApp').directive('requiresAuthorization', function() {
   return {
    template: '<div ng-if=\'iAmInRole\' ng-transclude></div>',
    restrict: 'E',
    transclude: true,
    scope: {
        role: '@'
    },
    controller: function($scope, UserService) {
       $scope.iAmInRole = (UsersService.myRoles.indexOf($scope.role) !== -1);
    }
  };
});

This works, but the content contained within the directive loses its scope, specifically the scope of the controller of the view it's found within. What am I overlooking?

jsfiddle for reference: http://jsfiddle.net/HbAmG/8/ Notice how the auth value isn't displayed inside the directive, but is available outside directive.

John Slegers
  • 45,213
  • 22
  • 199
  • 169
JagWire
  • 269
  • 2
  • 16

3 Answers3

4

Both ng-if and ng-transclude directives perform transclusion in your directive. In this case build-in transclude mechanism does not work fine and you should implement ngIf of yourself to make it work as expected:

JavaScript

app.directive('requiresAuthorization', function () {
    return {
        template: '<div ng-transclude></div>',
        restrict: 'E',
        transclude: true,
        scope: {
            role: '@'
        },
        controller: function ($scope) {
            $scope.iAmInRole = true;
        },
        link: function(scope, element, attr, ctrl, transcludeFn) {
            transcludeFn(function(clone) { // <= override default transclude
                element.empty();
                if(scope.iAmInRole) { // <= implement ngIf by yourself
                  element.append(clone);
                }
            });
        }
    };
});

Plunker: http://plnkr.co/edit/lNIPoJg786O0gVOoro4z?p=preview

If ng-show is an option for you to use instead of ng-if it may be a very simple workaround as well. The only side effect is that hidden data will be presented in the DOM and hidden using CSS .ng-hide {display: none !important;}.

JSFiddle: http://jsfiddle.net/WfgXH/3/

This post may also be useful for you since it describes the similar issue: https://stackoverflow.com/a/22886515/1580941

Community
  • 1
  • 1
Vadim
  • 8,701
  • 4
  • 43
  • 50
  • You get the transclude function passed into the link function, so there's no need for the controller. And be careful: @ExpertSystem might downvote your answer ;) – a better oliver Apr 21 '14 at 17:22
  • @zeroflagL Thanks, I've just forgot about the option of using fifth argument of linking function. Updating the post accordingly. – Vadim Apr 21 '14 at 17:33
  • @Vadim I don't suppose this is documented anywhere? I've gone through much of the angularjs guide and couldn't find any notes on nesting transclusions or not to use ng-if in directives, etc... – JagWire Apr 22 '14 at 14:00
2

You use ng-if. It does transclusion as well, unfortunately using a child scope of it's own scope, which in turn is the isolate scope.

Below are the screenshots from Batarang. The first is your code with ng-if. 4 is the isolate scope, 6 the transcluded content.

Scopes with ng-if

The same without ng-if. The transcluded content is now 5 and a sibling of the isolate scope and, more importantly, child of the controller's scope.

Scopes without ng-if

a better oliver
  • 26,330
  • 2
  • 58
  • 66
  • Do you have a recommendation/work-around on what to use instead? – JagWire Apr 21 '14 at 16:53
  • @JagWire Forget `ng-if`. Easy solution: use `ng-show`. Advanced solution: `requiresAuthorization` does what `ng-if` does itself. That's what's usually done by those kind of directives. – a better oliver Apr 21 '14 at 16:58
  • @zeroflagL: I really wonder how you came u with all that stuff. It has nothing to do with `ngIf`. – gkalpak Apr 21 '14 at 17:00
  • @zeroflagL: I did (and I read your answer) and still didn't learn anything. – gkalpak Apr 21 '14 at 17:22
  • @ExpertSystem Respect! It takes courage to admit that. I've added screenshots. And if you don't mind, please, give me my points back now. – a better oliver Apr 21 '14 at 17:50
  • @zeroflagL: The answer is not worth of a downvote anymore :) But a mention to `ngIf`'s own transcluding (which is the root of the problem) would be in place (imo). – gkalpak Apr 21 '14 at 18:14
  • @ExpertSystem My answer explained exactly that. But fair enough. I rephrased the sentence. – a better oliver Apr 21 '14 at 18:37
2

Once you define the scope property in your directive, it becomes an isolated scope. With no access to the outside (well, in a way, the only way is ugly and should be avoided), except to the stuff you pass into it via the scope property.

You'll need to either pass them into the directive: updated your jsfiddle

<requires-authorization role='Admin' data-auth-value='authValue' data-unauth-value='unAuthValue'>
  <div>Inside directive. For Admin eyes only</div>
  <p>{{authValue}}</p>
</requires-authorization>

// your directive scope
scope: {
  role: '@',
  authValue: '=',
  unauthValue: '='
}

Or create a service/factory to act as a middle man to communicate.

nowk
  • 32,822
  • 2
  • 35
  • 40
  • This option is rather simple, but be aware of a serious limitation: it does not propagate the whole outer scope to transcluded one, and each time someone wants to use another property of outer scope in transcluded context - implementation of directive should be changed, making it not reusable for general purpose. – Vadim Apr 21 '14 at 18:00