Search code examples
angularjsscopedirective

Why isn't my angular scope restricted in this directive?


I have the following directives below that create tab functionality. Unfortunately when I create multiple instances of these tabs things go wrong. The first set of tabs only switch the display of the second set of tabs. What am i missing?

http://jsfiddle.net/U3pVM/19281/

core.directive("pane", function () {
        return {
            require: "^tabs",
        restrict: "A",
        transclude: true,
        scope: {
            title: "@"
        },
        link: function (scope, element, attrs, tabsCtrl) {
            tabsCtrl.addPane(scope);
        },
        template:
          '<div class="tab-pane" ng-class="{active: selected}"' +
            'ng-transclude></div>',
        replace: true
    };
});

core.directive("tabs", function () {
    return {
        restrict: "A",
        transclude: true,
        //$scope is injected in to a controller via dependency injection hence the use of $scope not scope 
        controller: function ($scope, $element) {
            var panes = $scope.panes = [];

            $scope.select = function (pane) {
                angular.forEach(panes, function (pane) {
                    pane.selected = false;
                });
                pane.selected = true;
            };

            this.addPane = function (pane) {
                if (!panes.length) $scope.select(pane);
                panes.push(pane);
            };
        },
        template:
          '<div class="tabs">' +
            '<ul class="nav nav-tabs">' +
              '<li ng-repeat="pane in panes"' +
                  'ng-class="{active:pane.selected}">' +
                '<a href="" ng-click="select(pane)">{{pane.title}}</a>' +
              '</li>' +
            '</ul>' +
            '<div class="tab-content" ng-transclude></div>' +
          '</div>',
        replace: true
    };
});

Solution

  • You need to add an isolated (or child) scope on the tabs directive.

    core.directive("tabs", function () {
        return {
            restrict: "A",
            transclude: true,
            scope: {}, // <== here it is!
            controller: function ($scope, $element) {
                var panes = $scope.panes = [];
    
                $scope.select = function (pane) {
                    angular.forEach(panes, function (pane) {
                        pane.selected = false;
                    });
                    pane.selected = true;
                };
    
                this.addPane = function (pane) {
                    if (!panes.length) $scope.select(pane);
                    panes.push(pane);
                };
            },
            template: '',
            replace: true
        };
    });
    

    This is because you are now using the parent scope and therefor are overriding the panels property on that scope for every tabs directive you use. If you use an isolated scope you create a new array of panels for each tabs directive.

    See the updated fiddle: http://jsfiddle.net/basslagter/U3pVM/19287/

    You can also go with a child scope but that depends on whether or not you want to use properties of the parent scope. I'd go with isolated scope for now.