Maxim Shoustin
Maxim Shoustin

Reputation: 77904

The attributes passed to directive in AngularJS change only into directive scope but not outside

I want to use a directive to customize my code.

I have created a button to switch isCollapsedUpload flag defined in the controller as: @scope.isCollapsedUpload=false.

When the user presses the button, the isCollapsedUpload turns to true or vice versa and the icon changes.

From the controller:

$scope.switcher = function (booleanExpr, trueValue, falseValue) {
    return booleanExpr ? trueValue : falseValue;  
}

$scope.isCollapsedUpload = false;  

<button class="btn" ng-click="isCollapsedUpload = !isCollapsedUpload">                
    <span>Upload file</span>
    <i class="{{ switcher( isCollapsedUpload, 'icon-chevron-right', 'icon-chevron-down' )}}"></i>
</button>

I wrote this directive:

feederliteModule.directive('collapseExtend', function() {
return {
    restrict: 'E',
    scope: { isCollapsed:'@collapseTarget' },
    compile: function(element, attrs)
    { 
        var htmlText =
        '<button class="btn" ng-click="isCollapsed = !isCollapsed">'+       
        '       <span>'+attrs.label+'</span>'+  
        '       <i class="{{ switcher(isCollapsed, \'icon-chevron-right\', \'icon-chevron-down\' )}}"></i>'+  
        '</button>';

        element.replaceWith(htmlText);
    }
  }
});

And now I can use it like:

<collapse-extend 
              collapse-target="isCollapsedUpload"
              label="Upload file"                  
></collapse-extend>

It doesn't work. No icon changes. No errors,

isCollapsedUpload flag doesn't change. It changes only into directive

Did I miss something?

Upvotes: 0

Views: 760

Answers (2)

Dan
Dan

Reputation: 63059

EDIT: See a complete working fiddle with multiple buttons


I suggest using a service instead of a controller to maintain your model data. This allows you better separation of concerns as your app gets more complex:

var feederliteModule = angular.module('feederliteModule', []);

feederliteModule.service('btnService', function(){
    this.isCollapsedUpload = false;
    this.isCollapsedSomething = false;
});

feederliteModule.controller('btnController', function($scope, btnService){
    $scope.isCollapsedUpload = btnService.isCollapsedUpload;
    $scope.isCollapsedSomething = btnService.isCollapsedSomething;
});

feederliteModule.directive('collapseExtend', function() {
    return {
        restrict: 'E',
        scope: {
            isCollapsed:'=collapseTarget',
            label:'@'
        },
        replace: true,
        link: function (scope, element, attrs){
            scope.switcher = function (booleanExpr, trueValue, falseValue) {
                return booleanExpr ? trueValue : falseValue;  
            };
            scope.toggleCollapse = function() {
               scope.isCollapsed = !scope.isCollapsed;
            }              
        },
        template: '<button class="btn" ng-click="toggleCollapse()">'+       
        '<span>{{label}}</span>'+
        '<i ng-class="switcher(isCollapsed, \'icon-chevron-right\', \'icon-chevron-down\')"></i>'+  
        '</button>'
    }
});

Also, notice that you must use '=' instead of '@' in order for isCollapsed to work as you expect. The answer above needs this as well.

Upvotes: 1

Nick Larsen
Nick Larsen

Reputation: 18877

The reason the class doesn't change correctly is because you are not linking the template properly. This is easy to fix if you use the built in functionality:

var feederliteModule = angular.module('feederliteModule', []);
feederliteModule.directive('collapseExtend', [function() {
  return {
    restrict: 'E',
    scope: { 
      isCollapsed:'=collapseTarget',
      label: '@'
    },
    template: '<button class="btn" ng-click="isCollapsed = !isCollapsed">'+       
                '<span>{{ label }}</span>'+  
                '<i ng-class="{ \'icon-chevron-right\': isCollapsed, \'icon-chevron-down\': !isCollapsed }"></i>'+  
              '</button>'
  }
}]);
feederliteModule.controller('test', ['$scope', function($scope) {
  $scope.isCollapsedUpload = false;
}]);

To the best of my understanding, by replacing the parent element, you were removing the isolate scope this object was tied to without creating a new one on the button itself.

Upvotes: 2

Related Questions