Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Production Build Errors #53

Closed
tuneis opened this issue Aug 10, 2018 · 8 comments
Closed

Production Build Errors #53

tuneis opened this issue Aug 10, 2018 · 8 comments

Comments

@tuneis
Copy link

tuneis commented Aug 10, 2018

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)

package.json

{
  "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
  }
}

app.module.ts

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

map.component.html

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

map.component.ts

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.');
  }
}

Any chance anyone else is experiencing this problem?

@dgrald
Copy link

dgrald commented Aug 17, 2018

Yup, I have the same problem when adding and removing the map from the page. Here's the stack trace when there's an error adding a map:

main.f5db1ad2eafa5ab83d5e.js:1 Error: Failed to initialize WebGL
   at i._setupPainter (main.f5db1ad2eafa5ab83d5e.js:1)
   at new i (main.f5db1ad2eafa5ab83d5e.js:1)
   at t.createMap (main.f5db1ad2eafa5ab83d5e.js:1)
   at e._next (main.f5db1ad2eafa5ab83d5e.js:1)
   at e.__tryOrUnsub (main.f5db1ad2eafa5ab83d5e.js:1)
   at e.next (main.f5db1ad2eafa5ab83d5e.js:1)
   at e._next (main.f5db1ad2eafa5ab83d5e.js:1)
   at e.next (main.f5db1ad2eafa5ab83d5e.js:1)
   at e._next (main.f5db1ad2eafa5ab83d5e.js:1)
   at e.next (main.f5db1ad2eafa5ab83d5e.js:1)
A.fire @ main.f5db1ad2eafa5ab83d5e.js:1
polyfills.a9618ce7e9ca70fc00bf.js:1 Uncaught TypeError: Cannot read property 'resize' of undefined
   at i.resize (main.f5db1ad2eafa5ab83d5e.js:1)
   at new i (main.f5db1ad2eafa5ab83d5e.js:1)
   at t.createMap (main.f5db1ad2eafa5ab83d5e.js:1)
   at e._next (main.f5db1ad2eafa5ab83d5e.js:1)
   at e.__tryOrUnsub (main.f5db1ad2eafa5ab83d5e.js:1)
   at e.next (main.f5db1ad2eafa5ab83d5e.js:1)
   at e._next (main.f5db1ad2eafa5ab83d5e.js:1)
   at e.next (main.f5db1ad2eafa5ab83d5e.js:1)
   at e._next (main.f5db1ad2eafa5ab83d5e.js:1)
   at e.next (main.f5db1ad2eafa5ab83d5e.js:1)
polyfills.a9618ce7e9ca70fc00bf.js:1 Uncaught TypeError: Cannot read property 'resize' of undefined
   at i.resize (main.f5db1ad2eafa5ab83d5e.js:1)
   at i._onWindowResize (main.f5db1ad2eafa5ab83d5e.js:1)
   at e.invokeTask (polyfills.a9618ce7e9ca70fc00bf.js:1)
   at t.runTask (polyfills.a9618ce7e9ca70fc00bf.js:1)
   at t.invokeTask [as invoke] (polyfills.a9618ce7e9ca70fc00bf.js:1)
   at _ (polyfills.a9618ce7e9ca70fc00bf.js:1)
   at m (polyfills.a9618ce7e9ca70fc00bf.js:1)

I see this periodically on Chrome Version 68.0.3440.106 (Official Build) (64-bit).

@Wykks
Copy link
Owner

Wykks commented Aug 20, 2018

@dgrald doesn't seems to be the same issue. "Failed to initialize WebGL".
Check chrome://gpu/ on chrome / about:support on firefox

@dgrald
Copy link

dgrald commented Aug 20, 2018

Gotcha, thanks, @Wykks ! Sorry for the confusion.

@tuneis
Copy link
Author

tuneis commented Aug 21, 2018

This starting happening on development build as well. A colleague of mine found a temporary fix that works but is not sure if it is a permanent solution to the problem.

Line 544 of fesm5/ngx-mapbox-gl.js is currently this.

    /**
     * @param {?} control
     * @return {?}
     */
    MapService.prototype.removeControl = /**
     * @param {?} control
     * @return {?}
     */
    function (control) {
        var _this = this;
        return this.zone.runOutsideAngular(function () {
            _this.mapInstance.removeControl(/** @type {?} */ (control));
        });
    };

If you replace it with this it seems to resolve it when using ng serve to run your angular app.

    /**
     * @param {?} control
     * @return {?}
     */
    MapService.prototype.removeControl = /**
     * @param {?} control
     * @return {?}
     */
    function (control) {
        var _this = this;
        return this.zone.runOutsideAngular(function () {
            if(control)
            {
                _this.mapInstance.removeControl(/** @type {?} */ (control));
            }
        });
    };

@tuneis
Copy link
Author

tuneis commented Aug 21, 2018

The above 'fix' doesn't resolve the issue but I think I've narrowed down the problem. It seems that including the mglGeolocate control is causing the initial error stated above.

<mgl-control mglGeolocate position="top-left"></mgl-control>

If we take the following portion of the initial stack trace:

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

And we go to the source definition for the onRemove function for mapbox-gl.js it contains a bunch of code related to a users geo location. I noticed that there is no control on the page for user's geolocation so I suspect this is why the control it's trying to remove is undefined.

Removing the mglGeolocate control fixes my issue for development and production builds. I'm not sure how to fix the actual library for this but I hope this helps someone experiencing the same issue.

Wykks pushed a commit that referenced this issue Sep 9, 2018
@Wykks
Copy link
Owner

Wykks commented Sep 9, 2018

Have you added mapbox-gl-geocoder package ? It's needed when using mglGeolocate.

I added a check to not call removeControl if the control has not been initialized.

(This may also happen if the control is destroyed before the map is created).

@tuneis
Copy link
Author

tuneis commented Sep 9, 2018

Yes I've added the mapbox-gl-geocoder package.

@Wykks
Copy link
Owner

Wykks commented Oct 9, 2018

Closing, safety check released.

@Wykks Wykks closed this as completed Oct 9, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants