How to use Angular directives ng-click and ng-clas

2019-01-21 17:47发布

问题:

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?

回答1:

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



回答2:

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; },          
});


回答3:

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.



回答4:

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


回答5:

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!



回答6:

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)