17

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?

Ashley
  • 211
  • 1
  • 3
  • 10
  • 1
    Try to make your code examples fit within the page width so we don't have to scroll or use plunkr, this is unreadable without a lot of effort – Maarten Jul 17 '13 at 20:17

6 Answers6

28

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

Xowap
  • 521
  • 5
  • 9
10

You can use the new support for Angular content in angular-leaflet-directive:

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>';

... 

$scope.markers.push( { lat: ...,
                       lng: ...,
                       message: html,
                       getMessageScope: function() { return $scope; },          
});
David
  • 9,635
  • 5
  • 62
  • 68
  • 1
    This works really well! I wonder why there's no example on the site... However, `compileMessage` might also have to be set to `true` in the object you pass to the `push` method in the latest version of the plugin. Also, this only works for markers and not paths etc. – sibbl May 05 '15 at 12:52
1

I found this answer help for mapbox-gl-js and angular:

var html = '<button ng-click="fn()">Click Me</button>';
var compiledHtml = $compile(html)($scope);
var popup = new mapboxgl.Popup()
 .setLngLat([-91.874, 42.760])
 .setDOMContent(compiledHtml[0])
 .addTo(map);

Enable ng-click on a popup (mapbox)

shi11i
  • 1,528
  • 13
  • 16
0

I worked on this particular type of problem, to instantiate a function against a click inside a popup. I was using angular-leaflet-directive where I could not do a $compile as it was already inside a $compile, and would create a circular reference. What I did was the following.

$scope.markers[$scope.asset_table[imc]['asset']['_id']['$oid']]={
                        lat:$scope.asset_table[imc]['last_data']['loc']['coordinates'][1],
                        lng:$scope.asset_table[imc]['last_data']['loc']['coordinates'][0],
                        focus:true,
                        message:"<h4>"+$scope.asset_table[imc]['asset']['name']+"</h4>
                        <span class='btn btn-danger' 
                        onclick='detailView()';>View Details</span>",
                        //message: linkFunction, /*I discarded this as it was creating circular reference*/
                        icon: {
                            iconUrl: $scope.icon,
                            iconSize: [30, 30],
                            iconAnchor: [15, 15],
                            popupAnchor: [0, 0],
                            shadowUrl: $scope.icon,
                            shadowSize:[0,0],
                            iconAngle:$scope.asset_table[imc]['last_data']['bearing']
                        }
                    };

And below that, the function viewDetail() was called

detailView = function(){
    //alert("Test");
    $rootScope.$broadcast('asset.details',$scope.asset_table);
}

So I had to use traditional javascript inside angular to avoid the complicacy of circular linking.

tor9ado
  • 361
  • 2
  • 12
  • Note sure what you have done here. How do you get the data relevant to the specific popup from your asset_table? – mstreffo May 04 '14 at 09:52
  • Every asset_table[imc] has a unique message, where there is a span based bootstrapped button with an onclick instead of ng-click, which triggers the detailView() function, which is written in plain javascript. This triggers the broadcast. – tor9ado May 11 '14 at 13:24
0

I'm a bit late to the topic, but I had a similar situation and wanted to share my solution.

Suppose you have some html that you want to add into your popup, simply add an 'id' into it.

var html = '<a id="popup" href="" ng-model="featureSelected" ng-click="cherryPickPin(featureSelected)">cherry pick this pin</a>';

now all you need to do is this:

var popupElement = $('#popup'); // grab the element (make sure that it exists in the DOM)
popupElement = $compile(popupElement)($scope); // let angular know about it
Bwyss
  • 1,736
  • 3
  • 25
  • 48
0

I arrived on this post because I was looking for a solution of this problem on Ionic/Cordova (map integration on a mobile app).

The problem is the fact that $compute function doesn't seems to work, because it runs with a lite version of jQuery. So the only way, and clearly the simpliest, to make it work is with the code proposed by David (Thank you so much mate !).

In my code, it looks like this :

$scope.map.markers[i] = {
    lat: parseFloat(place.latitude),
    lng: parseFloat(place.longitude),
    message: htmlPopupContent,
    getMessageScope: function() { return $scope; },
    focus: true,
    draggable: false
};

Hope this could help!

Tunaki
  • 132,869
  • 46
  • 340
  • 423
SylvainAr
  • 154
  • 5