1

I have an ng-class initialization issue when I move the code which was in my controller, but related to the DOM, to a directive. To be short, here is some code:

view.html

<div ng-repeat="foo in foos" ng-click="rowClick($index)>
    <div ng-class="changeOpacity($index)>
        <span>{{foo.title}}</span>
        <span>{{foo.name}}</span>
        <span>{{foo.date}}</span>
        ...
        // some div with hidden content. Show when clicking on the div
    </div>
<div>

controller.js

$scope.rowClick = function (idx) {
    // unfold the clicked div to show content
};
$scope.changeOpacity = function (idx) {
    if (this is the div clicked) {
        return {dark: true};
    } else {
        return {light: true};
}

So when I have a list of div. When I clicked on a div, all the other divs get darker excepts this one.

Everything is working fine when $scope.rowClick and $scope.changeOpacity are in my controller.

When I move rowClick and changeOpcaity to a directive:

myBar.js

myApp.directive('myBar', function () {
    ...
    return {
        restrict:'A',
        link: function (scope, element) {
           element.bind('click', function () {
               ...
               same code from rowClick
               ...
               scope.changeOpacity = function () {
                   ...
                   same code from changeOpacity
               }
               scope.$apply();
            }
        }),
        changeOpacity: function () {
            ...
            return {light: true} // for all divs
        }
    }
});

Now my view.html is something like that:

<div ng-repeat="foo in foos" ng-click="rowClick($index) my-bar>
    <div ng-class="changeOpacity($index)>
        <span>{{foo.title}}</span>
        <span>{{foo.name}}</span>
        <span>{{foo.date}}</span>
        ...
        // some div with hidden content. Show when clicking on the div
    </div>
<div>

But now the divs are not initialized anymore with the ng-class. I have to click one time on each div to initialize it, with the link function which listen to the click.

How can I initialize the ng-class inside the directive ?

Thanx.

2 Answers 2

3

The problem is that you are putting the changeOpacity function in your scope only after a click. Do it in the link function instead like this:

link: function (scope, element) {
    scope.changeOpacity = function () {
        ...
        same code from changeOpacity
    }
}

making it a field of the "directive object" has no effect.

However, in my opinion it would be more elegant to use a model property to indicate if the element is active or not and change this property in an ng-click attribute. You can then refer to this property in the ng-class directive.

Something like this:

<div ng-repeat="foo in foos" ng-click="foo.active = true>
    <div ng-class="{light: active, dark: !active }>
        <span>{{foo.title}}</span>
        <span>{{foo.name}}</span>
        <span>{{foo.date}}</span>
        <div ng-show="foo.active">
            // some div with hidden content. Show when clicking on the div
        </div>
</div>

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

1 Comment

Definitely do this one. It's easy to try and jQuery everything, but in angular, you should really only change your model, and let angular change the UI for you. One improvement to this code I might offer is the ng-click should call a method on the scope and pass foo as a param, so that you can darken all other foos except the one you clicked.
2

ng-repeat creates a child scope for each item within the array. That means scope within a directive within ng-repeat will be the child scope. Thus you can set properties directly on that child scope and use ng-class, ng-show etc

Following is very basic example of directive to toggle state based on ng-click handler

HTML

<div ng-repeat="foo in foos" ng-click="rowClick($index)"  my-directive parent-array="foos">
    <div ng-class="{activeClass: foo.isActive}" class="item">
        <span>{{foo.name}}</span>
        <div ng-show="foo.isActive">Hidden content {{$index+1}}</div>
    </div>
</div>

Directive

 app.directive('myDirective',function(){
    return function(scope,elem,attrs){
      /* ng-repeat adds properties like `$first` , `$last` that can be helpful*/
      /* set default first element active*/
      if(scope.$first){
        scope.foo.isActive=true;
      }
      var parentArray=scope[attrs.parentArray];
      /* update active state across array*/
      scope.rowClick=function(idx){
        angular.forEach(parentArray,function(item, index){
          item.isActive = idx==index
        })
      }
    }
  })

DEMO

Here's another approach that stores currSelected item in the parent scope ( controller)

 app.directive('myDirective',function(){
    return function(scope,elem,attrs){

      /* set default first element active*/
      if(scope.$first){
        scope.foo.isActive=true;
        scope.$parent.currSelected=scope.foo
      }

      /* reset previous activeitem  and make item clicked the active one*/
      scope.rowClick=function(item){
        scope.$parent.currSelected.isActive=false
        scope.$parent.currSelected=item;
        item.isActive=true
      }
    }
  })

DEMO

2 Comments

Thumbs up for first approach, but I would advise against the second one; accessing $parent is almost always the wrong way to do angular. The first example you give is both cleaner and more straightforward.
@Hylianpuffball agree, service is better... was put here mostly to understand scopes. Too much to explain including a service inn this post as well. And I wanted to stay away from islolated scope as well

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.