4

I've been playing around with angular trying to understand how it manages scopes, then I found that I couldn't update the variables in the directive using a function call.

To illustrate the issue, here is my simple app: The idea is that when you click the toggle link, the menu should show up and when you click it again or somewhere else, the menu should disappear.

angular.module('app', [])
  .controller('DemoController', ['$scope', function($scope) {

  }])
  .directive('dropdown', function() {
    return {
      restrict: 'E',
      transclude: true,
      controller: function($scope) {
        $scope.onBlur = function () {
          // this doesn't actually work
          $scope.showMenu = false;
        };
      },
    };
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body id="body" ng-app="app" ng-controller="DemoController">
  <dropdown ng-transclude>
    <a href="#" ng-click="showMenu = !showMenu" ng-blur="onBlur()">Toggle</a>
    <menu ng-show="showMenu">
      <div>I'm</div>
      <div>the drop-down</div>
      <div>menu</div>
    </menu>
  </dropdown>
</body>

Here's my plunker links:

Non-working version: http://plnkr.co/edit/rfdt5FEoGAOX15RZUsKA?p=preview

Working version: http://plnkr.co/edit/xMANGDVa8n64OKK3gOgg?p=preview

The difference between the working one and the other is that the working version simply uses ng-blur="showMenu = false" instead of calling the function. If I call $scope.$apply() inside the controller function, then I will get "$apply already in progress" exception.

I guess I must be missing something here but I've no idea why right now. Thanks in advance.

CodingIntrigue
  • 75,930
  • 30
  • 170
  • 176
NSF
  • 2,499
  • 6
  • 31
  • 55

1 Answers1

2

Firs one thing about $scope.$apply() function it is possible to call it only when $scope.$$phase is empty (make sure its not in $digest or $apply phase)

if (!$scope.$$phase) {
   $scope.$apply()
}

In your first plunker change only this:

$scope.showMenu = false;
to 
this.showMenu = false;

its in function and function is already $scope bind.

Aleksandar Gajic
  • 1,349
  • 1
  • 18
  • 23
  • wow that totally does the work! By using console.log, it seems that inside the function call, "this" refers to the original scope and $scope is a new scope. I wonder if there's documentation mentioning why the behavior is like that. – NSF Jun 09 '15 at 07:29
  • This is problem with javascript, not actually angularjs. You have defined function under the $scope, and inside this function "this" is same as that original $scope, but if you use $scope in function javascript binds this variable with totally different $scope. – Aleksandar Gajic Jun 09 '15 at 07:31
  • This is what i got - console.log(this): ChildScope {$$childTail: null, $$childHead: null, $$nextSibling: null, $$watchers: Array[1], $$listeners: Object…}, console.log($scope): Object {$$childTail: ChildScope, $$childHead: ChildScope, $$nextSibling: null, $$watchers: null, $$listeners: Object…} May I ask where the second scope comes from? – NSF Jun 09 '15 at 07:51
  • @NSF The second scope is created when you use `ng-transclude`. I know it's long-winded, but this answer is superb: http://stackoverflow.com/questions/16653004/confused-about-angularjs-transcluded-and-isolate-scopes-bindings – CodingIntrigue Jun 09 '15 at 07:53
  • @AlexG Actually I later figured out that wasn't an issue with javascript but still angular. When transclude is used, angular creates a sibling scope not under but next to the directive scope. When the function gets called, angular binds the scope in the transcluded template to the "this" reference in that function. The $scope var is still the directive scope intact, and because it's a sibling scope, the change doesn't get prototypically inherited to the other one. – NSF Jun 10 '15 at 07:07