2

I've created a tab control using angular directives. It is consist of tab, tab-item directives with new scope and tab-item-header, tab-item-body directives for which scope is not declared.

If I understand correctly these directives use scope of tab-item directive because they are placed inside it. But when I try to get in markup property index which is declared inside tab-item scope it is undefined.

app.directive('tabItemHeader', function(){
return {
    require: '^tabItem',
    transclude: true,
    template: '<div ng-click="$parent.setCurrentTab(index)" ng-transclude></div>',
};});

app.directive('tabItemBody', function(){
return {
    require: '^tabItem',
    transclude: true,
    template: '<div ng-show="index==$parent.currentTabIndex"><div ng-transclude></div></div>'
};});

I've created a plunk http://plnkr.co/edit/HkXIOt8FKMw4Ja2GZtF1?p=preview to demonstrate it.

What is wrong?

2 Answers 2

5

(EDIT) Giving some thought after the conversation in the comments, I came up with a better solution. Here is the modified plunk:

http://plnkr.co/edit/djNk8PPzXvngZOvAirMu?p=preview

The key points of this implementation are:

  • Every directive transcludes its content. This means that, even the innermost directives have access to the outer scope, as is expected. So no more $parent.$parent... awfulness.

  • Every directive has an isolated scope. As per the docs the isolated scope is side-by-side with the transcluded one; therefore all the private state of the directives (in this case the active tab, the index of each tabItem and some directive-specific functions) is kept in the isolated scope.

  • The directives communicate through the controllers. This pattern requires a top level "coordinator" (here the tab for all descendant directives and the tabItem for the tabItemHeader and tabItemBody).

By the way, if you want tabs, I would suggest Angular UI.


This was a crazy puzzler.

The reason for your problem is that the tabItem directive had no reason to transclude its contents; this transclusion created a sibling scope that totally messed up your logic!

Thus the answer is simple: remove these lines from the tabItem directive:

// REMOVE THEM!!!
transclude: true,
template: '<div ng-transclude></div>',

A plunkr with these lines commented out that prints scope ids: http://plnkr.co/edit/bBfEej145s1YjcE9n3Lj?p=preview (this helps with debugging; see what happens when you include these lines, the templates and the linker function see different scopes!)

And your plunkr forked, with those lines commented out: http://plnkr.co/edit/tgZqjZk0bRCyZiHKM0Sy?p=preview

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

6 Comments

Thanks a lot, Nikos! And I want to ask you one more question about this code. For now directive's scope override controller's scope and I should use controller's variables in this way {{$parent.$parent.$parent.name}}. But it looks terrible. What is the best way to solve this problem?
Yes, you are right. Actually I just removed $parent from your plunkr (i.e. template: '<div ng-click="setCurrentTab(index)" ...>) and it works. Can you do that and verify it actually works? (There were some network instabilities and I have doubts)
Yes. It is possible to remove $parent from directive template. And I've fixed it. But when I want to use name property of the controller inside tab-item-body directive i should write the following: {{$parent.$parent.$parent.name}} (please see the first plunk). My question is if there is a way to do this without referencing parent scope?
Yes, still a puzzler! One answer is to remove the scope from the tab directive. And use directly the variable of the parent scope (Plunk: plnkr.co/edit/Of2kIcaGs7DLHyYi2mAK?p=preview).
I gave some thought and you are very right. Please see the edit at the top for a better solution - the way you originally wanted it.
|
3

http://www.undefinednull.com/2014/02/11/mastering-the-scope-of-a-directive-in-angularjs/

This site explains scope inheritance very well.

Basically, if you add a scope section to the directive that you want to have you have a few options:

scope: false # this inherits the parent scope and allows upstream and downstream traffic (parent <=> child)
scope: true # this inherits the parent scope and allows downstream traffic (parent => child)
scope: {} # this does not inherit and creates the directive's very own scope (you can still inherit by specifying what you want to come down)

More details on specifying scope to inherit via the last option: What is the difference between & vs @ and = in angularJS

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.