0

Scope of an AngularJS scope variable

I have this in my HTML

{{searchDialog.visible}}

and this in my controller

$scope.searchDialog = {'visible' : false};   // initialize

$scope.MarkerClicked = function(e)
{
    $scope.searchDialog.visible = true;
}

These are the only mentions of $scope.searchDialog. I beakpoint the $scope.MarkerClicked function and watch the value toggle to true, but the HTML never changes.

What am I doing wrongly? Is there a scope problem (no pun intended)?

Aha!! $scope.MarkerClicked is a callback from clicking a Leaflet map marker

var marker = L.marker(pubLatLng,
    {
        draggable: false,
        title: title,
        icon: new L.DivIcon({
            html: '<img class="my-div-image" src="js/3rd_party/leaflet/images/'
                       + iconImage + '" />'
                + '<span style="color:red">' + companyName + '</span>',
        })
    }).addTo($scope.map).on('click', $scope.MarkerClicked);

Is that somehow causing my problem? I can't see how, as the marker is added in a $scope function and the callback also handled in a $scope function.

Community
  • 1
  • 1
Mawg says reinstate Monica
  • 38,334
  • 103
  • 306
  • 551
  • 2
    The click handler happens outside of angular's digest cycle - someone with a similar problem [here](https://stackoverflow.com/q/18785400/3063706) – Danny Nov 18 '19 at 19:52
  • 1
    That's the answer - add a call to `$scope.$digest();` in the click handler! Thanks a 1,000,000. If you post an answer, i will award it; or I could just delete this as a dupe - your call. – Mawg says reinstate Monica Nov 18 '19 at 20:12
  • 1
    That's OK - I'm happy to help anyone that writes a question as clear as yours. :) – Danny Nov 18 '19 at 20:14
  • 1
    I briefly considered if there were a way to add the `ng-click` handler after the fact since it would work within the digest cycle already - I would have added an answer for that - it just feels more Angulary. But I wasn't sure how messy that would get I took the easy way out with the manual `$apply()`/`$digest()` – Danny Nov 18 '19 at 20:17
  • 1
    Something that looks like [this](https://stackoverflow.com/a/29926306/3063706), although since I've never done it this way myself I couldn't personally vouch for it – Danny Nov 18 '19 at 20:21
  • I suppose that I *could* wrap each map marker in a span, to give it an Id and add ng-click for those later, but the quick & dirty $scope.digest() works just fine. – Mawg says reinstate Monica Nov 18 '19 at 20:21

2 Answers2

1

This problem is related to Angular Digest Cycle, as MarkerClicked method was called, but it was outside the scope of Angular Digest Cycle, so You have to explicitly called digest cycle.

And to solve this issue please take a look at below code snippet, where if we don't add $scope.$apply(), $scope.message will not get updated, and after adding $scope.$apply() in below example which automatically triggers $rootScope.$digest(). As a result, the watchers are fired as usual and the view updates.

/* What happens with $apply */ 
angular.module('myApp',[]).controller('MessageController', function($scope) {
    
      $scope.getMessage = function() {
        setTimeout(function() {
          $scope.$apply(function() {
            //wrapped this within $apply
            $scope.message = 'Fetched after 3 seconds'; 
            console.log('message:' + $scope.message);
          });
        }, 2000);
      }
      
      $scope.getMessage();
    
    });
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.16/angular.min.js"></script>

<body ng-app="myApp">
  <div ng-controller="MessageController">
    Delayed Message: {{message}}
  </div>  
</body>
Sarvesh Mahajan
  • 914
  • 7
  • 16
1

Events created by third-party applications need to be brought into the AngularJS framework using $scope.$apply:

var marker = L.marker(pubLatLng,{
    draggable: false,
    title: title,
    icon: new L.DivIcon({
        html: '<img class="my-div-image" src="js/3rd_party/leaflet/images/'
                   + iconImage + '" />'
            + '<span style="color:red">' + companyName + '</span>',
    })
}).addTo($scope.map).on('click', function(e) {
    $scope.$apply(function() {
        $scope.MarkerClicked(e)
    });
});

AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc... You can also use $apply() to enter the AngularJS execution context from JavaScript.

AngularJS Developer Guide - Integration with the browser event loop

georgeawg
  • 48,608
  • 13
  • 72
  • 95