29

I am developing a directive which shows and hides it's contents based on a click event (ng-click) defined in it's template. On some views where the directive is used I'd like to be able to know if the directive is currently showing or hiding it's contents so I can respond to the DOM changes. The directive has isolated scope and I am trying to notify the parent scope when the directive has been "toggled". I'm attempting to accomplish this by passing a callback function to the directive where it is used that can be called when the directive's state changes i.e hides or shows

I'm not sure how to correctly implement this being that the state of the directive (hidden or shown) is stored in the directive's isolated scope and is determined after the ng-click. Therefore I need to call the parent scope's function from within the directive and not from withing the view.

This will make WAAY more sense with an example. Here is a plunked demonstrating what I'd like to do:

http://plnkr.co/edit/hHwwxjssOKiphTSO1VIS?p=info

var app = angular.module('main-module',[])

app.controller('MainController', function($scope){
  $scope.myValue = 'test value';
  $scope.parentToggle = function(value){
    $scope.myValue = value;
  };
});

app.directive('toggle', function(){
    return {
            restrict: 'A',
            template: '<a ng-click="toggle();">Click Me</a>',
            replace: true,
            scope: {
                OnToggle: '&'
            },
            link: function($scope, elem, attrs, controller) {
                $scope.toggleValue = false;
                $scope.toggle = function () {
                    $scope.toggleValue = !$scope.toggleValue;
                    $scope.OnToggle($scope.toggleValue)
                };
            }
        };
});

I'm relatively new to Angular. Is this a bad idea to begin with? Should I be using a service or something rather than passing around function refs?

Thanks!

isherwood
  • 58,414
  • 16
  • 114
  • 157
Nick
  • 19,198
  • 51
  • 185
  • 312

1 Answers1

51

Update

You can also use & to bind the function of the root scope (that is actually the purpose of &).

To do so the directive needs to be slightly changed:

app.directive('toggle', function(){
  return {
    restrict: 'A',
    template: '<a ng-click="f()">Click Me</a>',
    replace: true,
    scope: {
      toggle: '&'
    },
    controller: function($scope) {
      $scope.toggleValue = false;
      $scope.f = function() {
        $scope.toggleValue = !$scope.toggleValue;
        $scope.toggle({message: $scope.toggleValue});
      };
    }
  };
});

You can use like this:

<div toggle="parentToggle(message)"></div>

Plunk


You could bind the function using =. In addition ensure the property name in your scope and tag are matching (AngularJS translates CamelCase to dash notation).

Before:

scope: {
  OnToggle: '&'
}

After:

scope: {
  onToggle: '='
}

Furthermore don't use on-toggle="parentToggle({value: toggleValue})" in your main template. You do not want to call the function but just passing a pointer of the function to the directive.

Plunk

Sebastian
  • 16,813
  • 4
  • 49
  • 56
  • @Nick I digged a bit deeper and added an alternative using `&` to bind the function of the root scope. Maybe this is more *Angularish*. – Sebastian May 05 '14 at 17:50
  • Your plunker does exactly what I'm trying to do, but I'm having trouble porting it in to my code. Is there a reason it wouldn't work for an Element-type directive? Or is there anything I would need to do differently? – stranger Sep 19 '14 at 21:11
  • Not sure what you mean by element-type directive. In case want to use an element (`restrict: 'E'`) instead of an attribute (`restrict: 'A'`) in your HTML this would work in the same way (e.g. ``). – Sebastian Sep 20 '14 at 23:05
  • I'm having the same issue but trying to use a method in the grand-parent scope. It works fine when I'm accessing the method from the child directive, but doesn't "bubble up" from the grand-child directive... – Pierre-Adrien Oct 23 '14 at 09:23
  • oh, man - I've spent 4 hours on this until I found this answer! Works well! – Arman Bimatov Jan 14 '15 at 09:08
  • These two line $scope.toggle({message: $scope.toggleValue}); and
    gave me solution for my problem. Thanks.
    – Prashanth Apr 14 '16 at 10:15
  • 1
    This is definitely one of the best, most straight-forward examples I've seen of this. Thanks! – Jerad Oct 03 '16 at 16:00