How to use Angular directives ng-click and ng-class inside Leaflet marker popup

Ashley picture Ashley · Jul 15, 2013 · Viewed 10.3k times · Source

I'm using Angular.JS and Leaflet.JS for a map in my location that has map markers with popups binded to them. I need to use a span with two different icons (one shown in code below) that you can click to call different functions and with ng-class to change the class if certain conditions are met. This is my code:

var marker = L.marker([51.5, -0.09], {icon: blueIcon}).bindPopup('<br><span ng-class="thumbsUpClass(' + hotelsSelectedDates[i]['hotels'][s] + ')" ng-click="addChoice(' + hotelsSelectedDates[i]['hotels'][s] + ',' + hotels + ')"><span class="popup-container"><span class="icon-stack thumbs-up-stack"><i class="icon-sign-blank icon-stack-base"></i><i class="icon-thumbs-up"></i></span></span></span>');

However when I inspect the element I get this:

<span ng-class="thumbsUpClass([object Object])" ng-click="addChoice([object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object])"><span class="popup-container"><span class="icon-stack thumbs-up-stack"><i class="icon-sign-blank icon-stack-base"></i><i class="icon-thumbs-up"></i></span></span></span>

The ng-click should send that function both the specific object and the array of objects but when I click the icon nothing happens. In my research I found that the popup prevents event propagation (more info but I'm not sure how to override it or a fix to get it to work with angular. Would anyone have an idea of how to accomplish this?

UPDATE:

Since ng-click/class evaluate a string I fixed the variables to be like this:

$scope.item = hotelsSelectedDates[i]['hotels'][s]
$scope.set = hotels
var marker = L.marker([51.5, -0.09], {icon: blueIcon}).bindPopup('<br><span ng-class="thumbsUpClass(item)" ng-click="addChoice(item,set)"><span class="popup-container"><span class="icon-stack thumbs-up-stack"><i class="icon-sign-blank icon-stack-base"></i><i class="icon-thumbs-up"></i></span></span></span>');

The html then comes out correctly:

<span ng-class="thumbsUpClass(item)" ng-click="addChoice(item,set)"><span class="popup-container"><span class="icon-stack thumbs-up-stack"><i class="icon-sign-blank icon-stack-base"></i><i class="icon-thumbs-up"></i></span></span></span>

However when I click the icon nothing happens and it doesn't look like the functions are being called. Anyone have any clue why this would happen?

Answer

Xowap picture Xowap · Dec 5, 2013

Your issue comes from the fact that you are manually creating some DOM, which was not compiled by AngularJS.

In those cases, you have to manually compile and link the element.

The code would look like this:

var html = '<br><span ng-class="thumbsUpClass(item)" ' +
    'ng-click="addChoice(item,set)"><span class="popup-container"><span ' +
    'class="icon-stack thumbs-up-stack"><i class="icon-sign-blank ' +
    'icon-stack-base"></i><i class="icon-thumbs-up"></i></span></span></span>',
    linkFunction = $compile(angular.element(html)),
    newScope = $scope.$new();

newScope.item = hotelsSelectedDates[i]['hotels'][s]
newScope.set = hotels
var marker = L.marker([51.5, -0.09], {icon: blueIcon}).bindPopup(linkFunction(newScope)[0]);

Here I take your HTML string, and I start by transforming it into DOM. Because AngularJS eats DOM, not strings.

angular.element(html)

Then, I compile this DOM into a link function, using the $compile service.

linkFunction = $compile(angular.element(html));

When executed, this function will return a jQuery DOM tree fully controlled by Angular, running in the scope you give to it as argument. This is what I do here

linkFunction(newScope)

Please note that the scope I give is a child scope of $scope. Without doing this, you would share the same scope between all popups, and this would not be a good idea. Creating the new scope was done in the var declaration

newScope = $scope.$new()

From that you can get the actual DOM node

linkFunction(scope)[0]

And pass it to Leaflet

.bindPopup(linkFunction(newScope)[0]);

And you're done!

For more info, please refer to the compiler doc.

EDIT: rectified issues regarding scope