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?

silent_coder
  • 6,222
  • 14
  • 47
  • 91
Natasha
  • 973
  • 9
  • 24

2 Answers2

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

Nikos Paraskevopoulos
  • 39,514
  • 12
  • 85
  • 90
  • 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? – Natasha Oct 01 '13 at 10:22
  • Yes, you are right. Actually I just removed `$parent` from your plunkr (i.e. `template: '
    `) and it works. Can you do that and verify it actually works? (There were some network instabilities and I have doubts)
    – Nikos Paraskevopoulos Oct 01 '13 at 11:06
  • 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? – Natasha Oct 01 '13 at 11:30
  • 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: http://plnkr.co/edit/Of2kIcaGs7DLHyYi2mAK?p=preview). – Nikos Paraskevopoulos Oct 01 '13 at 11:59
  • I've already thought about this solution but still try to find another. If I will find something else I leave a comment here. Anyway thanks for your help! – Natasha Oct 01 '13 at 12:19
  • 1
    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. – Nikos Paraskevopoulos Oct 02 '13 at 07:54
4

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

Community
  • 1
  • 1
peoplespete
  • 1,104
  • 1
  • 11
  • 15