11

I have a directive and a controller:

app.directive('responseBox', function(){
return {
    restrict: 'E',
    transclude: true,
    templateUrl: 'responseBox.html',
    link: function(scope, element, attrs) {
        element.bind("click", function () {
            scope.toggle();
        })
    }
}});

and a controller:

app.controller('responseBoxCtrl', function($scope) {
$scope.opened = false;
$scope.toggle = function() {
    $scope.opened = !$scope.opened;
    console.log($scope.opened);
}});

responseBox.html:

<div class="promptBlockResponse" ng-transclude>
<div class="btn-toolbar" style="text-align: right;">
    <div class="btn-group" ng-show="opened">
        <a class="btn btn-link" href="#"><i class="icon-pencil icon-white"></i></a>
        <a class="btn btn-link" href="#"><i class="icon-remove icon-white"></i></a>
    </div>
</div>          

And in the main html file:

<response_box ng-controller="responseBoxCtrl"></response_box>

I want the btn-group to show when the opened variable is true. When I click the responseBox I can see the variable toggling, but the btn-group does not show/hide. What am I missing?

dndr
  • 2,319
  • 5
  • 18
  • 28
  • Do you maybe have a css property interfering like `display:none;`? – Bart Apr 08 '13 at 21:36
  • 4
    Add `scope.$apply()` to your click callback after calling `scope.toggle()`. The click handler runs "outside" of Angular, so you need to cause Angular to run a digest cycle. – Mark Rajcok Apr 08 '13 at 21:36
  • That worked, thanks! BTW, there's probably a more 'angular' way to set this up then? With hg-click for example? – dndr Apr 08 '13 at 23:06
  • 1
    Yeah, with `ng-click="toggle()"` on the root element in your template you can eliminate your `link` function entirely. It's canonical - and simpler. – Josh David Miller Apr 08 '13 at 23:21
  • Also, you seem to be missing a closing ``. – Kevin Beal Jun 07 '13 at 16:46

2 Answers2

30

So repeating what Josh and I said in the comments above, the click handler runs "outside" of Angular, so you need to call scope.$apply() to cause Angular to run a digest cycle to notice the change that was made to scope (and then it will update your view):

$scope.toggle = function() {
    $scope.opened = !$scope.opened;
    console.log($scope.opened);
    $scope.$apply();
}});

The link function can be eliminated by using ng-click in the template:

<div class="promptBlockResponse" ng-transclude ng-click="toggle()">
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • Maybe I should open a new thread for this, but with this code, if I define multiple responseBoxes in the html, the toggle function will affect all btn-groups to open and close. I think this has something to do with scope, but I seem to fail to grasp the scope concept. When I define an isolated scope {} for each responseBox directive I get this error: Multiple directives [responseBox, ngController] asking for isolated scope on: – dndr Apr 09 '13 at 08:50
  • 1
    @JoostVanDoremalen, to support multiple response boxes, I suggest you have the directive scope manage the `opened` property. You can have the directive create either a child scope (`scope: true`), or an isolate scope (`scope: {...}`). The toggle function can be defined inside your directive, on `scope`. Here's a [fiddle](http://jsfiddle.net/mrajcok/w2bKX/) where I used `scope: true`, and I inlined the toggle code into the click callback function (instead of defining a method). Let me know if anything is unclear. – Mark Rajcok Apr 09 '13 at 14:42
  • Small typo in above: I believe that the method is $.scope.$.apply() not $.scope.apply() – Ben L Jul 26 '13 at 18:55
8

With Angular 1.3 and 1.2 the following snippet from an HTML template for a custom element directive:

<div ng-click="toggle($event)"></div>
<div ng-show="data.isOpen"></div>

And a snippet from the controller for that custom directive:

$scope.toggle = function ($event, destinationState) {
....
data.isOpen = true; //this is in scope and a digest cycle is already running
//calling $scope.$apply will cause an error

demonstrates an in scope scenario where you do need to use $apply.

I came across this SO question because I was using double brackets in my

<div ng-show="{{data.isOpen}}">

Changing to

 <div ng-show="data.isOpen"></div>

got my binding working when I thought at first I had a scope issue.

So in angular 1.2 and 1.3 ng-click is not "outside" of Angular, at least using the signature I used for my toggle function and is explained here: $apply already in progress error

I discovered my double bracket ng-show issue that I initially thought was a scope issue thanks to this SO: why doesn't ng-show remove class ng-hide

Community
  • 1
  • 1
Brian Ogden
  • 18,439
  • 10
  • 97
  • 176