Angularjs with leaflet directives - To clear markers - angularjs

I have following setup working fine, however, I got some small problem..
Tried many ways but I cannot get rid of markers before adding new markers..
With following example, marker keep being added whenever you push from the controller..what is the best way to erase existing markers before adding any new one...?
var module = angular.module('Map', []);
module.directive('sap', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function(scope, element, attrs) {
var map = L.map(attrs.id, {
center: [-35.123, 170.123],
zoom: 14
});
//create a CloudMade tile layer and add it to the map
L.tileLayer('http://{s}.tile.cloudmade.com/57cbb6ca8cac418dbb1a402586df4528/997/256/{z}/{x}/{y}.png', {
maxZoom: 18
}).addTo(map);
//add markers dynamically
var points = [];
updatePoints(points);
function updatePoints(pts) {
for (var p in pts) {
L.marker([pts[p].lat, pts[p].long]).addTo(map).bindPopup(pts[p].message);
}
}
scope.$watch(attrs.pointsource, function(value) {
updatePoints(value);
});
}
};
});
And inside the controller, the way to add new marker is
$scope.pointsFromController.push({
lat: val.geoLat,
long: val.geoLong
});
Code in the HTML is simple
<sap id="map" pointsource="pointsFromController"></sap>

Leaflet creator here. The best way to clear markers is to add them to a group (instead of directly adding to the map), and then call group.clearLayers() when you need. http://leafletjs.com/examples/layers-control.html

Assuming pointsFromController is a simple javascript array.
You can simply undo the pushes you've made.
$scope.removePoints = function() {
for (var i=$scope.pointsFromController.length; i>=0; i--)
$scope.pointsFromController.pop();
};

Related

why to make directive in angularjs not renderning second time?

why my google directive not working ? Actually I make a simple directive of google map .And display on view.it work first time .But not work for second time .I will explain more When I run my plunker it show me Qutub minar google map.But when I click
‘+’ icon and press done button add another location example “Delhi” it give me longitude and latitute but not display the map
here is my code
Issue on this fuction I think
this.loadMap = function(latLng) {
console.log("function latlng called");
console.log(latLng);
google.maps.visualRefresh = true;
var myCenter = new google.maps.LatLng(latLng.latitude, latLng.longitude);
var map = new google.maps.Map(document.getElementById("id"), mapProp);
var marker = new google.maps.Marker({
position: myCenter,
});
marker.setMap(map);
}
You actually have two major troubles :
You generate multiple div with the same id (googleMap).
When you run the directive it get element by id but there is multiple elements with this ID which cause it to mess.
The main problem is that generating the google-map shouldn't be the responsibility of your "pane" directive but should be a totally new directive instead.
You can see it working in this plunker :
What i did :
I create a new directive myGoogleMap which basically use your old loadMap function (That i removed from the tabs directive). I also took the "latlng" attribute from the pane and put it on this directive.
.directive('myGoogleMap', function(){
return {
scope: {
latlng: '='
},
link: function(scope, element, attrs){
var latLng = scope.latlng;
console.log("function latlng called");
console.log(latLng);
google.maps.visualRefresh = true;
var myCenter;
myCenter=null;
myCenter = new google.maps.LatLng(latLng.latitude, latLng.longitude);
var mapProp = {
center: myCenter,
zoom: 15,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(element[0], mapProp);
var marker = new google.maps.Marker({
position: myCenter,
});
marker.setMap(map);
}
}
The main difference is that the directive that generate the google map is already on the good element. I'll be able to access this element with element[0] in the link function. That mean i can actually generate a google map everywhere else giving a latlng object and using this directive.
Now your ng-repeat for pane looks like this :
<div ng-repeat="tabInfo in tabPlaces.tabItems" pane title="{{ tabInfo.title}}" ng-show="tabInfo.selected">
<p>{{ tabInfo.content }}</p>
<div my-google-map latlng="tabInfo.latlng" ng-style="mapConfig" style="height:200px;width:100%"></div>
</div>
Hope it helped.

Floating title is not working in Angularjs

I have a list of item with two iterations. I want a sticky title when the title scroll up from the view area. I have done it with jquery, but can't able to do in angular. Created a fiddle https://jsfiddle.net/1vf5ska7/
I just to want to add a class in tag when the title is goes up to the view area.
angular.element(document.querySelector('#l-content__desc__split1__body')).on('scroll', function() {
});
And the important thing is it is not a window scroll. It's a div scroll
Please help me.
Thanks..
You need to include a directive and operate on it. If $window.pageYOffset is greater than the position of the element you apply a specific class to that element which is positioned fixed.
var app = angular.module('app', []);
app.directive('setClassOnTop', function ($window) {
var $win = angular.element($window); // wrap window object as jQuery object
return {
restrict: 'A',
link: function (scope, element, attrs) {
var title = angular.element(document.getElementById('sticky-title'));
var offsetTop = title[0].offsetTop;
$win.on('scroll', function (e) {
if ($window.pageYOffset > offsetTop) {
angular.element(title[0]).addClass('floating-title');
} else {
angular.element(title[0]).removeClass('floating-title');
}
});
}
};
});
And here is the updated fiddle:
https://jsfiddle.net/1vf5ska7/3/

Pass coordinates from service to directive with angular

I made a directive for my google maps on my angular application. The latitude and longtitude are hard coded in the directive. I'm trying to get those coordinates from my service just the same as I get the location name and description.
<h2 class="page-title">{{ locationInfo.name }}</h2>
<img ng-src="{{ locationInfo.images[0].scenic}}" alt="">
<p>{{ locationInfo.description }}</p>
<gmap id ="map-canvas" class="map"></gmap>
My directive is currently looking like this which gives a static map instead of changing dynamically
.directive("gmap", function () {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function(scope, element, attrs) {
var latLng = new google.maps.LatLng(40, -73);
var mapOptions = {
zoom: 15,
center: latLng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById(attrs.id), mapOptions);
var marker = new google.maps.Marker({
position: latLng,
map: map
});
}
}
});
First, instead of using document.getElementById you should make use of the "element" atribute which you inject in your link function. That "element" is your angular object corresponding to your DOM element. If you can't use it, then go ahead using document.getElementById, but change the "id" dynamically.
Also, if you want to use it as a directive I would suggest to use two attributes for your latitude and longitude, and naming your id dynamically using some property of your locationInfo object:
<gmap id ="map-canvas_{{locationInfo.name}}" lat="locationInfo.lat" lon="locationInfo.lon" class="map"></gmap>
where locationInfo contains latitude and longitude info named as lat and lon.
Then in your directive, you read your tag atritubes via the attrs object:
.directive("gmap", function () {
return {
...
// Here you read your tag attributes "lat" and "lon"
link: function(scope, element, attrs) {
var latLng = new google.maps.LatLng(attrs.lat, attrs.lon);
// Here you can use the injected "element" instead of "document.getElementById(attrs.id)" but it seems it doesn't work for you
var map = new google.maps.Map(document.getElementById(attrs.id), mapOptions);
}
}
});
Also I recomend to define your variables using comma "," as in:
var a = something,
b = somethingElse,
c = anything;
instead of using several "var" sentences. This is a good practice.
Hope this helps.

Issue with included JavaScript when running Testacular unit test on Angular.js directive

I'm trying to write a unit test for a directive that I use to insert a Leaflet.js map.
This tag
<div ng-controller="WorldMapCtrl">
<sap id="map"></sap>
</div>
Is used with this controller
function WorldMapCtrl($scope) {}
And the following directive
angular.module('MyApp').
directive('sap', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function(scope, element, attrs) {
var map = L.map(attrs.id, {
center: [40, -86],
zoom: 2
});
//create a CloudMade tile layer and add it to the map
L.tileLayer('http://{s}.tile.cloudmade.com/57cbb6ca8cac418dbb1a402586df4528/997/256/{z}/{x}/{y}.png', {
maxZoom: 4, minZoom: 2
}).addTo(map);
}
};
});
This correctly inserts the map into the page. When I run the following unit test, it crashes out with
TypeError: Cannot read property '_leaflet' of null
describe('directives', function() {
beforeEach(module('MyApp'));
// Testing Leaflet map directive
describe('Testing Leaflet map directive', function() {
it('should create the leaflet dir for proper injection into the page', function() {
inject(function($compile, $rootScope) {
var element = $compile('<sap id="map"></sap>')($rootScope);
expect(element.className).toBe('leaflet-container leaflet-fade-anim');
})
});
});
});
From what I can tell from some Googling, the error seems to be fairly common. Perhaps not putting the tag in properly (With an id="map") or something similar can cause the problem. However, I'm not seeing what I can change with the test for this directive. Any ideas what I'm doing wrong here?
I've also included the necessary JS files in testacular.conf.js in the same order that they're included in index.html. So, it's not an issue of the files not being there (I assume at least).
One result from Googling (sadly, it doesn't help): https://groups.google.com/forum/#!msg/leaflet-js/2QH7aw8uUZQ/cWD3sxu8nXsJ

angularjs with Leafletjs

Following directie code is from http://jsfiddle.net/M6RPn/26/
I want to get a json feed that has many lat and long.. I can get a json with $resource or $http in Angular easily but How can I feed it to this directive to map thing on the map?
module.directive('sap', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function(scope, element, attrs) {
var map = L.map(attrs.id, {
center: [40, -86],
zoom: 10
});
//create a CloudMade tile layer and add it to the map
L.tileLayer('http://{s}.tile.cloudmade.com/57cbb6ca8cac418dbb1a402586df4528/997/256/{z}/{x}/{y}.png', {
maxZoom: 18
}).addTo(map);
//add markers dynamically
var points = [{lat: 40, lng: -86},{lat: 40.1, lng: -86.2}];
for (var p in points) {
L.marker([points[p].lat, points[p].lng]).addTo(map);
}
}
};
});
I don't know a lot about Leaflet or what you're trying to do, but I'd assume you want to pass some coordinates in from your controller to your directive?
There are actually a lot of ways to do that... the best of which involve leveraging scope.
Here's one way to pass data from your controller to your directive:
module.directive('sap', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function(scope, element, attrs) {
var map = L.map(attrs.id, {
center: [40, -86],
zoom: 10
});
//create a CloudMade tile layer and add it to the map
L.tileLayer('http://{s}.tile.cloudmade.com/57cbb6ca8cac418dbb1a402586df4528/997/256/{z}/{x}/{y}.png', {
maxZoom: 18
}).addTo(map);
//add markers dynamically
var points = [{lat: 40, lng: -86},{lat: 40.1, lng: -86.2}];
updatePoints(points);
function updatePoints(pts) {
for (var p in pts) {
L.marker([pts[p].lat, pts[p].lng]).addTo(map);
}
}
//add a watch on the scope to update your points.
// whatever scope property that is passed into
// the poinsource="" attribute will now update the points
scope.$watch(attr.pointsource, function(value) {
updatePoints(value);
});
}
};
});
Here's the markup. In here you're adding that pointsource attribute the link function is looking for to set up the $watch.
<div ng-app="leafletMap">
<div ng-controller="MapCtrl">
<sap id="map" pointsource="pointsFromController"></sap>
</div>
</div>
Then in your controller you have a property you can just update.
function MapCtrl($scope, $http) {
//here's the property you can just update.
$scope.pointsFromController = [{lat: 40, lng: -86},{lat: 40.1, lng: -86.2}];
//here's some contrived controller method to demo updating the property.
$scope.getPointsFromSomewhere = function() {
$http.get('/Get/Points/From/Somewhere').success(function(somepoints) {
$scope.pointsFromController = somepoints;
});
}
}
I recently built an app using Angular JS and Leaflet. Very similar to what you've described, including location data from a JSON file. My solution is similar to blesh.
Here's the basic process.
I have a <map> element on one of my pages. I then have a Directive to replace the <map> element with the Leaflet map. My setup is slightly different because I load the JSON data in a Factory, but I've adapted it for your use case (apologies if there are errors). Within the Directive, load your JSON file, then loop through each of your locations (you'll need to setup your JSON file in a compatible way). Then display a marker at each lat/lng.
HTML
<map id="map" style="width:100%; height:100%; position:absolute;"></map>
Directive
app.directive('map', function() {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function(scope, element, attrs) {
var popup = L.popup();
var southWest = new L.LatLng(40.60092,-74.173508);
var northEast = new L.LatLng(40.874843,-73.825035);
var bounds = new L.LatLngBounds(southWest, northEast);
L.Icon.Default.imagePath = './img';
var map = L.map('map', {
center: new L.LatLng(40.73547,-73.987856),
zoom: 12,
maxBounds: bounds,
maxZoom: 18,
minZoom: 12
});
// create the tile layer with correct attribution
var tilesURL='http://tile.stamen.com/terrain/{z}/{x}/{y}.png';
var tilesAttrib='Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under CC BY SA.';
var tiles = new L.TileLayer(tilesURL, {
attribution: tilesAttrib,
opacity: 0.7,
detectRetina: true,
unloadInvisibleTiles: true,
updateWhenIdle: true,
reuseTiles: true
});
tiles.addTo(map);
// Read in the Location/Events file
$http.get('locations.json').success(function(data) {
// Loop through the 'locations' and place markers on the map
angular.forEach(data.locations, function(location, key){
var marker = L.marker([location.latitude, location.longitude]).addTo(map);
});
});
}
};
Sample JSON File
{"locations": [
{
"latitude":40.740234,
"longitude":-73.995715
},
{
"latitude":40.74277,
"longitude":-73.986654
},
{
"latitude":40.724592,
"longitude":-73.999679
}
]}
Directives and mvc in angularJs are different technologies. Directives are usually executed when the page loads. Directives are more for working on/with html and xml. Once you have JSON, then its best to use the mvc framework to do work.
After the page has rendered, to apply directives you often need to do $scope.$apply() or $compile to register the change on the page.
Either way, the best way to get a service into a directive is by using the dependency injection framework.
I noticed the scope:true, or scope:{} was missing from your directive. This has a big impact on how well the directive plays with parent controllers.
app.directive('mapThingy',['mapSvc',function(mapSvc){
//directive code here.
}]);
app.service('mapSvc',['$http',function($http){
//svc work here.
}])
Directives are applied by camelCase matching. I would avoid using or because of an issue with IE. Alternative would be
<div map-thingy=""></div>
Assuming that in your controller you got
$scope.points = // here goes your retrieved data from json
and your directive template is:
<sap id="nice-map" points="points"/>
then inside your directive definition you can use the "=" simbol to setup a bi-directional binding between your directive scope and your parent scope
module.directive('sap', function() {
return {
restrict: 'E',
replace: true,
scope:{
points:"=points"
},
link: function(scope, element, attrs) {
var map = L.map(attrs.id, {
center: [40, -86],
zoom: 10
});
L.tileLayer('http://{s}.tile.cloudmade.com/57cbb6ca8cac418dbb1a402586df4528/997/256/{z}/{x}/{y}.png', {
maxZoom: 18
}).addTo(map);
for (var p in points) {
L.marker([p.lat, p.lng]).addTo(map);
}
}
};
});
Also instead of adding the markers right into the map, it's recomended to add your markers to a L.featureGroup first, and then add that L.featureGroup to the map because it has a clearLayers() metho, which will save you some headaches when updating your markers.
grupo = L.featureGroup();
grupo.addTo(map);
for (var p in points) {
L.marker([p.lat, p.lng]).addTo(grupo);
}
// remove all markers
grupo.clearLayers();
I hope this helps, cheers

Resources