简体   繁体   中英

Angular directive, binding click event to outside element not working

I am trying to create a custom event for toggling with Angular. The directive is called toggleable.

It sounds trivial at first, but tricky bit is that i want to be able to to use any button or link on the page for toggling.

The code i have written is as below

Directive

'use strict';

angular.module('onsComponents')
.directive('togglable', function() {
return {
  restrict: 'A',
  transclude: true,
  scope: {
    togglerId: '@',
    show: '@'
  },
  link: function(scope, element, attrs) {
    if (!scope.togglerId) {
      console.error("Toggler id must be given")
      return
    }

    scope.visible = (scope.show === 'true')
    scope.toggle = function() {
      alert("Before " + scope.visible)
      scope.visible = !scope.visible
      alert("After " + scope.visible)
    }

    //Bind click event to given dom element
    angular.element('#' + scope.togglerId).bind("click", function() {
      scope.toggle()
    })


  },
  templateUrl: 'app/components/toggler/toggler.html'
  }
  })

The Template

<div ng-transclude ng-show="visible">

</div>

The View

<a href="" id="toggleButton" >Click me to toggle</a>
<div togglable toggler-id="toggleButton">
  Hey
</div>

The toggle link seems to be working. I can see the alerts when the link is clicked. The trouble is the content does not appear. It seems like the link is not really is in the same scope with the content, another scope is created when i do this.

If i move the click link within the template as below, it works. But that is not really what i want.

<!-- Move the link inside template -->
<a href=""  ng-click="toggle()" >Click me to toggle</a>
<div ng-transclude ng-show="visible">

</div>

So how can i achieve that ?

It is more angular-esque to create two directives and a service that links them for this purpose. As a rough outline:

app.service('toggleService', function() {
    var toggleables = {};

    this.registerToggleable = function(key, f) {
        toggleables[key] = f;
    }

    this.toggle = function(key) {
        var f = toggleables[key];
        f();
    }
});

app.directive('toggler', function(toggleService) {
    return {
        link: function(scope, elem, attrs) {
            elem.bind("click", function() {
                toggleService.toggle(attrs.toggler);
                // because the event handler operates outside of angular digest cycle
                scope.$apply();
            });
        }
    }
})

app.directive('toggleable', function(toggleService) {
    return {
        link: function(scope, elem, attrs) {
            function toggle() {
                scope.visible = !scope.visible;
            }

            toggleService.registerToggleable(attrs.toggleable, toggle);
        }
    }
});

The above will need tweaking to actually do anything apart from set a variable on the scope, but you can then use the two like so:

<a href="" toggler="foo" >Click me to toggle</a>
<div toggleable="foo">
  Hey
</div>

This way you are declaring the toggle functionality on the elements themselves, with no ID-based lookup magic.

You have to call scope.$apply() every time you are changing stuff that Angular watches outside of Angular. By outside I mean events bound using the jQuery API - or any other way besides Angular's native ng-click etc.

So do either:

scope.toggle = function() {
    scope.visible = !scope.visible;
    scope.$apply();
}

Or:

scope.toggle = function() {
    scope.$apply(function() {
        scope.visible = !scope.visible;
    });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM