0

I am facing a strange situation in which I have created a directive, to which a controller is attached, and one of the two tiny functions of the controller is never called from the view whereas the other function is.

Here is the plunker.

The message I expect is (bold is what does not show up)

You are limited to: Prison

I have already created tens of directives, whether in their own right or as wrappers around existing directives available on GitHub, from lightweight ones such as custom-select to behemoths such as angular-ui-grid.

I am at the end of my wits here as to why {{getArea()}} produces no text at all in the view. I've scrutinized the code, trying to do it with new eyes, so to speak, and I see nothing wrong. I've created a specific project in Eclipse for this tiny piece of code, installed Wampserver just so I could set breakpoints in Firebug and God knows to what great lengths I had to go just so that I could understand what is wrong with the code I wrote.

For instance, in isRestricted(), I can call getArea() without any problem. However, Angular seems to not find the function from the directive.

A few similar questions have already been asked but none of the errors (missing controller or ng-app specification, missing dependency list at module declaration, nested controllers, etc.) seem to apply. There's obviously an important lesson to be learned here and I'm truly eager to learn it.

EDIT: The lesson learned is that ng-if creates a new scope. That new scope comes in between the controller and the directive, which leads to the template of the directive losing access to anything defined in the controller (at least, that's how I would phrase it). (Note that a comment hinted at directive priority.)

There are several solutions, which all maintain the prototypical inheritance needed for the template to access the functions defined in the controller:

  • not using an isolate scope
  • not defining the ng-if directive on the top-level element of my directive, as that causes a conflict (between my controller's scope and the scope defined by ng-if). I believe ng-if wins here, which leads to the controller's scope being out of reach of the directive. Using ng-if on a child div does the trick (because then, the ng-if scope inherits my controller's scope, hence making the functions available to the template).

Because of the CSS styling needed with this directive, I have used scope: false.

1
  • For information ng-if have a priority of 600. Since your directive has no priority, it will be 0. So ng-if definitively win. Commented Apr 20, 2016 at 12:45

4 Answers 4

2
<span class="scoop-badge-content">{{$parent.getArea()}}</span>

Or in directive :

scope:true

This is because ng-if use how own scope

The strange thing is that when i have this problem i usually use dot notation. But it doesn't work here, probably because we're inside a directive, and i didn't had the case until now.

EDIT : a last way of doing this chaging the template :

  <div class="scoop-badge scoop-badge-ua">
    <div  ng-if="isRestricted()">
        <span class="scoop-badge-title">You are limited to:</span>
        <span class="scoop-badge-content">{{getArea()}}</span>
    </div>
</div>

I think this work because you have replace true and ng-if will conflict with ng-scope if it's on the top DOM element.

Sign up to request clarification or add additional context in comments.

2 Comments

I knew about scopes, isolate scopes, scope inheritance and all, but the information I was missing is that ng-if creates a new scope. However, this still does not explain why isRestricted is "found" and called whereas getArea, which is defined at the same level and on the same scope, is not found and called. Could that be because isRestricted is used before ng-if creates its scope?
directives have priorities, if not set it's 0, check ng-id priority
1

When you have scope = {} in your directive, Angular creates an isolated scope. Which means it can't get to the getArea() function.

You can completely remove the scope = {} line or set it to scope = true or scope = false depending on what you're trying to achieve later on.

When set to scope = true Angular will create a new scope object and assign it to the directive. This scope object is prototypically inherited from its parent scope.

When set to scope = false the directive will use its parent scope. (This is the default value. It has the same effect if you remove this line).

More information about scopes here

4 Comments

You said "When you have scope = {} in your directive, Angular creates an isolated scope. Which means it can't get to the getArea() function." The thing I don't understand is why it can then get to isRequired.
Because replace: true is breaking your function when used with ng-if. You should use: transclude: true instead. With this you can keep the private scope scope: {} and still access the functions. The replace option is being deprecated in the upcoming Angular version.
I've edited the template to check what I was asserting (i.e. "why it can then get to isRequired") and I was wrong: isRequiredstill can't be reached. transclude: true does indeed work with the isolate scope.
@AbVog : transclude: true does the work, but in terms of readability I prefer not to show directives tags in the html code. Your code is entirely correct, you just need to wrap your template with <div></div>, take a look at my answer.
0

Removing scope: {} from directive definition solves problem.

    app.directive('scoopBadgeUa', function() {

        return {
            restrict : "A",
            scope: {}, // This is not needed, creates conflict
            templateUrl : "scoop-badge-ua.html",
            replace : true,
            controller : 'ScoopBadgeUaController',
        };
    });

Comments

0

Your code is correct, you don't have to do anything more than adding a <div></div> wrapping your code in scoop-badge-ua.html.

<div>
  <div class="scoop-badge scoop-badge-ua" ng-class="{'visible': isRestricted()}" ng-if="isRestricted()">
    <span class="scoop-badge-title">You are limited to:</span>
    <span class="scoop-badge-content">{{getArea()}}</span>
  </div>
</div>

enter image description here

I like this solution better than transclude: true because it doesn't include the directive's tag in your html code, which ultimately translates to a cleaner code.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.