When I load a page with a map on it, everything loads fine and the map shows up and you can do what you will with it. As soon as I leave the page and navigate to another page I get the below error.
ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'remove' of undefined
TypeError: Cannot read property 'remove' of undefined
at n.onRemove (mapbox-gl.js:33)
at o.removeControl (mapbox-gl.js:33)
at ngx-mapbox-gl.js:555
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:388)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:138)
at NgZone.push../node_modules/@angular/core/fesm5/core.js.NgZone.runOutsideAngular (core.js:3783)
at MapService.push../node_modules/ngx-mapbox-gl/fesm5/ngx-mapbox-gl.js.MapService.removeControl (ngx-mapbox-gl.js:554)
at ControlComponent.push../node_modules/ngx-mapbox-gl/fesm5/ngx-mapbox-gl.js.ControlComponent.ngOnDestroy (ngx-mapbox-gl.js:1222)
at callProviderLifecycles (core.js:9573)
at callElementProvidersLifecycles (core.js:9541)
at n.onRemove (mapbox-gl.js:33)
at o.removeControl (mapbox-gl.js:33)
at ngx-mapbox-gl.js:555
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:388)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:138)
at NgZone.push../node_modules/@angular/core/fesm5/core.js.NgZone.runOutsideAngular (core.js:3783)
at MapService.push../node_modules/ngx-mapbox-gl/fesm5/ngx-mapbox-gl.js.MapService.removeControl (ngx-mapbox-gl.js:554)
at ControlComponent.push../node_modules/ngx-mapbox-gl/fesm5/ngx-mapbox-gl.js.ControlComponent.ngOnDestroy (ngx-mapbox-gl.js:1222)
at callProviderLifecycles (core.js:9573)
at callElementProvidersLifecycles (core.js:9541)
at resolvePromise (zone.js:814)
at resolvePromise (zone.js:771)
at zone.js:873
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
at Object.onInvokeTask (core.js:3815)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:188)
at drainMicroTaskQueue (zone.js:595)
at ZoneTask.push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask [as invoke] (zone.js:500)
at invokeTask (zone.js:1540)
{
"name": "ncri-spa",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"dev": "ng build",
"production": "ng build --configuration=production",
"staging": "ng build --configuration=staging",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@amcharts/amcharts3-angular": "^1.5.0",
"@angular/animations": "^6.0.0",
"@angular/common": "^6.0.0",
"@angular/compiler": "^6.0.0",
"@angular/core": "^6.0.0",
"@angular/forms": "^6.0.0",
"@angular/http": "^6.0.0",
"@angular/platform-browser": "^6.0.0",
"@angular/platform-browser-dynamic": "^6.0.0",
"@angular/router": "^6.0.0",
"@aspnet/signalr": "^1.0.2",
"@auth0/angular-jwt": "^2.0.0",
"@mapbox/mapbox-gl-draw": "^1.0.9",
"@mapbox/mapbox-gl-geocoder": "^2.3.0",
"@mapbox/mapbox-gl-sync-move": "^0.2.0",
"core-js": "^2.5.4",
"jwt-decode": "^2.2.0",
"mapbox-gl": "^0.47.0",
"mapbox-gl-compare": "^0.2.0",
"ngx-mapbox-gl": "^2.0.0",
"primeng": "^6.1.0",
"rxjs": "^6.0.0",
"zone.js": "^0.8.26"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.6.1",
"@angular/cli": "~6.0.1",
"@angular/compiler-cli": "^6.0.0",
"@angular/language-service": "^6.0.0",
"@types/jasmine": "~2.8.6",
"@types/jasminewd2": "~2.0.3",
"@types/jwt-decode": "^2.2.1",
"@types/mapbox-gl": "^0.47.0",
"@types/node": "~8.9.4",
"codelyzer": "~4.2.1",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~1.7.1",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~1.4.2",
"karma-jasmine": "~1.1.1",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.3.0",
"ts-node": "~5.0.1",
"tslint": "~5.9.1",
"typescript": "~2.7.2"
},
"browser": {
"fs": false,
"path": false,
"os": false
}
}
NgxMapboxGLModule.withConfig({
// Can also be set per map (accessToken input of mgl-map)
accessToken: 'my access token',
// Optionnal, specify if different from the map access token, can also be set per mgl-geocoder (accessToken input of mgl-geocoder)
// geocoderAccessToken: 'TOKEN'
}),
<div style="width: 100%; height: 400px;">
<mgl-map #mbmap [style]="'mapbox://styles/mapbox/satellite-v9?optimize=true'" [zoom]="[3]" [center]="[-113.517209517767,50.950570]"
(load)="onLoad($event)" [attributionControl]="false">
<mgl-control mglNavigation></mgl-control>
<mgl-control mglFullscreen position="top-left"></mgl-control>
<mgl-control mglGeolocate position="top-left"></mgl-control>
<mgl-control mglScale unit="metric" position="bottom-right"></mgl-control>
<mgl-control position="bottom-left">
<div class='legend' [style.display]="max === 0 ? 'none' : 'block'">
<h4>Legend</h4>
<div class="legend-content">
<div [ngClass]="selectedGradient.class" class="legend-scale"></div>
<div class="legend-values">
<h5 *ngFor="let s of selectedGradient.colors; let i = index">{{(min + (i * ((max - min) / selectedGradient.colors.length))).toFixed(2)}}</h5>
<h5>{{max.toFixed(2)}}</h5>
</div>
</div>
</div>
</mgl-control>
</mgl-map>
</div>
import { HttpClient } from '@angular/common/http';
import { AppService } from '../../app.service';
import { Component, OnInit, Input, Output, OnChanges, SimpleChanges, EventEmitter } from '@angular/core';
import { } from 'mapbox-gl';
import { SharedComponent } from '../shared.component';
import { Map } from 'mapbox-gl';
@Component({
selector: 'app-mb-map',
templateUrl: './mb-map.component.html',
styleUrls: ['./mb-map.component.scss']
})
export class MbMapComponent extends SharedComponent implements OnInit, OnChanges {
// demo's of things you can do
// https://wykks.github.io/ngx-mapbox-gl/demo/edit/live-update-feature
/**
* THe layer id to remove and add to the map when changing data layers.
*/
layerId: number;
/**
* The source id to remove and add to the map when changing data layers.
*/
sourceId: number;
/**
* The mapbox map.
*/
map: Map;
/**
* The max value of the dataset.
*/
max: number;
/**
* The min value of the dataset.
*/
min: number;
/**
* The color gradients to apply to the map data with class names for legend.
*/
gradients: any[];
/**
* The selected gradient chosen to render.
*/
selectedGradient: any;
/**
* The points passed from a parent component.
*/
@Input('data') data: any;
// test files
testFile: string;
constructor(public appService: AppService, public httpClient: HttpClient) {
super(appService);
this.busy = false;
this.gradients = [
{ class: 'r2g', colors: ['rgba(238, 46, 47, 0.3)', 'rgba(254, 204, 47, 0.3)', 'rgba(40, 167, 69, 0.3)'] },
{ class: 'b2p', colors: ['rgba(22, 103, 225, 0.3)', 'rgba(98, 22, 225, 0.3)', 'rgba(225, 22, 213, 0.3)'] },
];
this.selectedGradient = this.gradients[0];
this.layerId = 0;
this.sourceId = 0;
this.min = 0;
this.max = 0;
}
/**
* Update map on changes
* @param changes
*/
ngOnChanges(changes: SimpleChanges): void {
if (changes.data && changes.data.currentValue !== changes.data.previousValue) {
if (this.data.points && this.data.points.features.length > 0) {
// set the min and max for the legend
this.min = this.data.min;
this.max = this.data.max;
// draw the marker on the map
// this.drawMarker(this.data.marker, this.data.points.features[0].geometry.coordinates);
// TODO: Add the layer name to the data that gets passed to the points as the layer id and source id instead of incrementing.
// Might be able to save multiple layers in the map once loaded
// so we can hide or show them without reloading the data.
// draw the geojson layer
this.drawGeoJsonPoints(
this.map,
this.data.points,
`sourceId-${this.sourceId}`,
`layerId-${this.layerId}`,
this.data.min,
this.data.max,
this.selectedGradient.colors
);
}
}
}
ngOnInit() {
}
drawMarker(title: string, coordinates: number[]) {
// check if marker exists
const exists = this.map.getLayer('marker');
// remove if it does
if (exists) {
this.map.removeLayer('marker');
}
// add new marker
this.map.addLayer({
'id': 'marker',
'type': 'symbol',
'source': {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': [{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': coordinates
},
'properties': {
'title': title,
'icon': 'monument'
}
}]
}
},
'layout': {
'icon-image': '{icon}-15',
'text-field': '{title}',
'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
'text-offset': [0, 0.6],
'text-anchor': 'top'
}
});
}
/**
* Add's a layer on the map load function.
* @param map The map.
*/
onLoad(map: Map) {
this.map = map;
}
/**
* Draw's a set of GeoJson points on the map.
* @param map The map to draw on.
* @param geojson The path to a GeoJson file or a string representing GeoJson data.
* @param sourceId An arbitrary unique id that separates this layer from any other added layers.
* @param min The minimum value of the data source.
* @param max The maximum value of the data source.
* @param rgbaGradient The color gradient to use.
*/
drawGeoJsonPoints(map: Map, geojson: any, sourceId: string, layerId: string, min: number, max: number, rgbaGradient: string[]) {
// check for existence of layer
const layerExists = map.getLayer(layerId);
// remove it if it does
if (layerExists) {
map.removeLayer(layerId);
console.log(`Layer with id of ${layerId} exists. Removing ${layerId}.`);
}
// check if source exists
const sourceExists = map.getSource(sourceId);
// remove it if it does
if (sourceExists) {
map.removeSource(sourceId);
console.log(`Source with id of ${sourceId} exists. Removing ${sourceId}.`);
}
// add a data source for the map data to load from
console.log('Adding source to map...');
map.addSource(sourceId, {
type: 'geojson',
data: geojson, // path to geojson file
cluster: true, // enable clustering
clusterMaxZoom: 6, // Max zoom to cluster points on
// clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
});
// get zone ranges for paint gradient
console.log('Calculating zone...');
const zone = (max - min) / rgbaGradient.length;
// calculate stop values for each color gradient
console.log('Calculating stop values for gradients from zone...');
const gradients = [];
for (let i = 0; i < rgbaGradient.length; i++) {
gradients.push(min + (i * zone));
gradients.push(rgbaGradient[i]);
}
// declare paint object prior
// add interpolate function
// interpolation type
// the function to retrieve the value to interpolate by
// the list of gradients and their stop values
console.log('Configuring paint interpolation...');
const paint = {
'circle-color': ['interpolate', ['linear'], ['get', 'value'], ...gradients]
};
console.log('Adding layer...');
map.addLayer({
id: layerId,
type: 'circle',
source: sourceId,
// filter: ['has', 'value'], // checks for nulls, if any are present it doesn't draw anything at all
paint: paint,
});
console.log('Map points loaded.');
}
}