Search code examples
angularjsgoogle-mapsevent-handlingng-map

Angular: Changing path inside event handler not working


I'm new to angular, and am having a problem changing the location inside of an event handler. The handler is for a marker positioned on a google map. I am using ng-map (which is pretty awesome).

Here's the code that creates the markers (this runs inside the success callback of an $http get):

for( i = 0; i < data.Pins.length; i++ )
{
    var marker = new google.maps.Marker({
        title: data.Pins[i].StreetAddress,
        position: new google.maps.LatLng(data.Pins[i].Latitude,data.Pins[i].Longitude),
        data: data.Pins[i],
        index: i,
    });

    google.maps.event.addListener(marker, 'click', function(tgt) {
        $scope.pinClicked(marker);
    });

    marker.setMap($scope.map);

    $scope.markers.push(marker);
}

The event handler is very simple:

$scope.pinClicked = function(marker) {
    if( marker.data.Homes.length == 1) $location.path("/home");
    else $location.path("/unit");
};

When I click on a marker the handler executes, and the if/then/else statement is executed. However, the location does not change.

Is this because I'm outside the "context" for angular because I set up the event listener through google maps?

Additional Info

Thanx for the suggestion about using window.location. What I didn't mention earlier is that the map is part of a partial view, so I don't want to trigger a page reload -- I want angular to update the partial view based on a location change. Which I know it can do, because $location.path('/unit') works just fine outside of that event handler.


Solution

  • The problem is that the Google Maps event callback runs outside the Angular context. Calling $location.path("...") is exactly correct (you should NOT use window.location in an Angular app), but you must use the location service within a callback that's under Angular's control. That's not happening here. Consider this excerpt from your code:

    $http.get(..., function() {
      for (i = 0; i < data.Pins.length; i++) { // This is your FOR loop 
        ...
        google.maps.event.addListener(marker, 'click', function(tgt) {
          $scope.pinClicked(marker);
        });
    

    The $http.get() callback (your function) fires within the Angular context; when your function returns, Angular triggers a $digest cycle, which updates all bindings. It's the $digest cycle that will process any $location changes.

    HOWEVER... The callback to google.maps.event.addListener fires later, outside the Angular context. Angular won't see any scope changes or $location changes you make within that callback (at least, not immediately—not until the next time a $digest cycle happens to run, whenever that is).

    You need to wrap your call to $scope.pinClicked(marker) within $scope.$apply, like so:

    google.maps.event.addListener(marker, 'click', function(tgt) {
      $scope.$apply(function() {
        $scope.pinClicked(marker);
      });
    });
    

    By wrapping the call in $scope.$apply, you've essentially told Angular, "Hey, run the code in this function, and then kick off a $digest cycle."