none
No Purpose Property in Bing Maps RRS feed

  • Question

  • We're using Angular 7 and Bing Maps V8 together.  We've seen an error (for an internally accessible app, and I can't share the link for code review, sorry) where our console shows:

    TypeError: Cannot read property 'purpose' of null

       at n._updateMapFrameData (de83dd5e.js?bu=B_FErwWxBbMF8wS9BYYF:1)

       at n_onLayerDataLoaded (de83dd5e.js?bu=B_FErwWxBbMF8wS9BYYF:1)


    The weird thing is that we're seeing this issue inconsistently across machines.  However, we're thinking that the updateMapFrameData object might need to check to see if it has a purpose (ha ha! Get it?).

    Any leads/help would be appreciated.


    Monday, September 16, 2019 5:53 PM

All replies

  • Hi Randy,

    There was a sample written by the infusion team that is available on GITHUB for using Bing Maps in Angular.  You can find that here https://github.com/infusion-code/angular-maps.  Are you doing something like this sample?

    Sincerely,

    IoTGirl


    Tuesday, September 17, 2019 3:00 AM
    Owner
  • Hi IoTGirl,

    We're not using that library, we're just following the basic Bing Maps implementation within an Angular module. We're not getting the error consistently, and until recently weren't getting it at all.  We're not sure we changed anything around the maps, but it's possible that a data change in our data model is triggering an update which is triggering some code in Bing Maps.  There just doesn't appear to be a null check before the purpose property is accessed, and we can't find anyone else having reported this.

    Rewards,

    Randy

    Tuesday, September 17, 2019 2:16 PM
  • Hello Randy,

    We have not had other reports of such behavior and there have been no recent changes to the v8 control (that I am aware of).

    When did you first start seeing these errors?

    What do you have any insight into which v8 operations you are performing when the error occurs? (e.g. Event being handled, Layer being added/added to, Infobox added to map/changed, pushpin added/changed.)

    Can you share the code you are using to call the v8 control script? (please obscure your Bing Maps key)?

    Hopefully this will shine some light on where this issue is coming from.

    Regards,

    Stephen

    Tuesday, September 17, 2019 6:17 PM
    Moderator
  • Ooh, let's see.  Here are some data bits:

    The Service

    import { Injectable } from '@angular/core';
    import { BehaviorSubject, Observable } from 'rxjs';
    
    @Injectable()
    export class BingMapService {
      public loaded$: BehaviorSubject<boolean> = null;
    
      private loaded = false;
      private loading = false;
    
      constructor() {
        this.loaded$ = new BehaviorSubject<boolean>(this.loaded);
      }
    
      public load(): Observable<boolean> {
        if (!this.loaded && !this.loading) {
          this.loading = true;
    
          const callback = 'GetMap';
          window[callback] = () => {
            if (!this.loaded) {
              this.loaded = true;
    
              this.loaded$.next(this.loaded);
            }
    
            this.loading = false;
          };
    
          const apiKey =
            'MySecretAPIKey';
    
          const script = document.createElement('script');
          script.src = `https://www.bing.com/api/maps/mapcontrol?callback=GetMap&key=${apiKey}`;
          script.type = 'text/javascript';
          script.async = true;
          script.defer = true;
    
          document.getElementsByTagName('head')[0].appendChild(script);
        }
    
        return this.loaded$;
      }
    }

    And the component we're binding to the Angular selector:

    // Please do not delete this reference path line. It is necessary to make Typescript happy with our Bing maps type definitions
    // tslint:disable-next-line no-reference
    /// <reference path="../../../node_modules/bingmaps/types/MicrosoftMaps/Microsoft.Maps.All.d.ts" />
    
    import {
      Component,
      Input,
      Output,
      EventEmitter,
      ElementRef,
      AfterViewChecked,
      OnChanges,
      OnDestroy,
      SimpleChanges,
      ChangeDetectorRef
    } from '@angular/core';
    import { Subscription, Observable, fromEvent } from 'rxjs';
    import { skip } from 'rxjs/operators';
    import { BingMapService } from './bing-map.service';
    
    declare var Microsoft: any;
    
    @Component({
      selector: 'our-super-secret-angular-selector-for-bing-maps',
      templateUrl: './bing-map.component.html',
      styleUrls: ['./bing-map.component.scss']
    })
    export class BingMapComponent
      implements AfterViewChecked, OnChanges, OnDestroy {
      @Input() options: Microsoft.Maps.IMapLoadOptions;
      @Input() layers: Microsoft.Maps.ILayer[];
      @Input() infoboxOptions: Microsoft.Maps.IInfoboxOptions;
      @Input() localizedMapTypes: {
        road: string;
        aerial: string;
      } = {
        road: 'Road',
        aerial: 'Aerial'
      };
      @Input() mapControlsEnabled = true;
      @Input() customControlEnabled = true;
      @Input() showTraffic: false;
    
      @Output() mapReady: EventEmitter<unknown> = new EventEmitter();
      @Output() mapTypeChange: EventEmitter<number> = new EventEmitter();
      @Output() zoomLevelChange: EventEmitter<number> = new EventEmitter();
    
      public mapStylesOpen = false;
      public mapStyleOptions: Array<{
        icon: string;
        name: string;
        mapType: any;
      }> = Array.from({ length: 3 });
      public selectedMapStyleOption = 0;
      public documentClickSubscription: Subscription;
      public showZoomControls = true;
      public showCustomControlButton = true;
    
      private initializing = false;
      private mapLoadSubscription: Subscription;
      private map: Microsoft.Maps.Map;
      private infobox: Microsoft.Maps.Infobox;
      private currentZoomLevel: number;
    
      constructor(
        public element: ElementRef,
        public changeDetectorRef: ChangeDetectorRef,
        private bingMapService: BingMapService
      ) {}
    
      public ngAfterViewChecked() {
        // Initialize the map if it hasn't been initialized and
        // is not in the process of being initialized
        if (!this.map && !this.initializing) {
          this.initialize();
        }
      }
    
      public async ngOnChanges(changes: SimpleChanges) {
          if (changes.localizedMapTypes && changes.localizedMapTypes.currentValue) {
            this.mapStyleOptions[0].name =
              changes.localizedMapTypes.currentValue.road;
            this.mapStyleOptions[1].name =
              changes.localizedMapTypes.currentValue.aerial;
          }
    
          // Change the map options when they change
          if (this.map && changes.options && changes.options.currentValue) {
            this.map.setView({
              ...changes.options.currentValue,
              streetsideOptions: {
                ...changes.options.currentValue.streetsideOptions,
                onErrorLoading: this.onErrorLoadingStreetside
              }
            });
          }
    
          // Replace the layers when they change
          if (this.map && changes.layers && changes.layers.currentValue) {
            this.map.layers.insertAll(changes.layers.currentValue);
          }
    
          // Set the infobox options when they change
          if (
            this.map &&
            changes.infoboxOptions &&
            changes.infoboxOptions.currentValue
          ) {
            this.infobox.setOptions(changes.infoboxOptions.currentValue);
          }
    
          // Respond when the showTraffic input changes
          if (this.map && changes.showTraffic) {
            if (changes.showTraffic.currentValue) {
              if (Microsoft.Maps.Traffic) {
                this.showTrafficManager();
              } else {
                Microsoft.Maps.loadModule(
                  'Microsoft.Maps.Traffic',
                  this.showTrafficManager
                );
              }
            }
          }
      }
    
      public ngOnDestroy() {
        this.mapLoadSubscription.unsubscribe();
      }
    
      public showTrafficManager() {
        const manager = new Microsoft.Maps.Traffic.TrafficManager(this.map);
        manager.setOptions({ legendVisible: false });
        manager.show();
      }
    
      public onErrorLoadingStreetside() {
        // equivalent for alerting the user here
        alert(
          'Streetside is not available in this location.. Reverting to Road view.'
        );
    
        this.selectMapStyleOption(0);
        this.changeDetectorRef.detectChanges();
      }
    
      public closeMapStylesSelector() {
        this.mapStylesOpen = false;
        this.changeDetectorRef.detectChanges();
      }
    
      public openMapStylesSelector() {
        this.mapStylesOpen = true;
        this.changeDetectorRef.detectChanges();
    
        // Create an observable that listens to document clicks
        // Ignore the first click since it will cause the selector to
        // immediately close otherwise.
        const documentClickListener: Observable<any> = fromEvent(
          document,
          'click'
        ).pipe(skip(1));
    
        // Close the selector and unsubscribe when the document is clicked
        this.documentClickSubscription = documentClickListener.subscribe(() => {
          this.closeMapStylesSelector();
    
          this.documentClickSubscription.unsubscribe();
        });
      }
    
      public selectMapStyleOption(index: number) {
        // Select the option and change the map type
        this.selectedMapStyleOption = index;
    
        this.map.setMapType(this.mapStyleOptions[index].mapType);
      }
    
      public getMap(): Microsoft.Maps.Map {
        return this.map;
      }
    
      public getInfobox(): Microsoft.Maps.Infobox {
        return this.infobox;
      }
    
      private initialize() {
        if (!this.map && !this.initializing) {
          this.mapLoadSubscription = this.bingMapService
            .load()
            .subscribe(async loaded => {
              if (loaded && !this.map && !this.initializing) {
                this.initializing = true;
    
                this.mapStyleOptions = [
                  {
                    icon: '',
                    name: 'Road',
                    mapType: Microsoft.Maps.MapTypeId.road
                  },
                  {
                    icon: '',
                    name: 'Aerial',
                    mapType: Microsoft.Maps.MapTypeId.aerial
                  },
                  {
                    icon: '',
                    name: 'Streetside',
                    mapType: Microsoft.Maps.MapTypeId.streetside
                  }
                ];
    
                // Create the map
                this.map = new Microsoft.Maps.Map(
                  this.element.nativeElement.children[0],
                  {
                    ...this.options,
                    streetsideOptions: {
                      ...this.options.streetsideOptions,
                      onErrorLoading: this.onErrorLoadingStreetside
                    }
                  }
                );
    
                // Add an event handler to know when the view was changed
                Microsoft.Maps.Events.addHandler(
                  this.map,
                  'viewchangeend',
                  this.mapViewChanged.bind(this)
                );
    
                // Add an event handler to know when the map type was changed
                Microsoft.Maps.Events.addHandler(
                  this.map,
                  'maptypechanged',
                  this.mapTypeChanged.bind(this)
                );
    
                // Insert any layers that have been passed into the component
                if (Array.isArray(this.layers) && this.layers.length) {
                  this.map.layers.clear();
                  this.map.layers.insertAll(this.layers);
                }
    
                // Create an infobox that will be reusable
                this.infobox = new Microsoft.Maps.Infobox(this.map.getCenter(), {
                  visible: false
                });
    
                this.infobox.setMap(this.map);
    
                // If infobox options were passed in, set them on the infobox
                if (this.infoboxOptions) {
                  this.infobox.setOptions(this.infoboxOptions);
                }
    
                // Show traffic if necessary
                if (this.showTraffic) {
                  Microsoft.Maps.loadModule('Microsoft.Maps.Traffic', () => {
                    this.showTrafficManager();
                  });
                }
    
                this.initializing = false;
    
                // Emit the mapReady event
                this.mapReady.emit();
              }
            });
        }
      }
    
      private mapTypeChanged() {
        const mapTypeId = this.map.getMapTypeId();
    
        // If the map type ID is streetside, hide the extra controls, otherwise show them
        if (mapTypeId === Microsoft.Maps.MapTypeId.streetside) {
          this.showZoomControls = false;
          this.showCustomControlButton = false;
        } else {
          this.showZoomControls = true;
          this.showCustomControlButton = true;
        }
    
        this.mapTypeChange.emit(mapTypeId);
        this.changeDetectorRef.detectChanges();
      }
    
      private mapViewChanged() {
        const newZoomLevel: number = this.map.getZoom();
    
        // If the zoom level changed, emit an event for the new zoom level
        if (this.currentZoomLevel !== newZoomLevel) {
          this.currentZoomLevel = newZoomLevel;
          this.zoomLevelChange.emit(newZoomLevel);
        }
      }
    }

    We pass in the map layers with their data, but that's where more of our secret sauce is. Let me know if you suspect the secret sauce has been laced with arsenic. 

    Rewards,

    Randy


    Friday, September 20, 2019 2:21 PM
  • And... here's a full(er) stack trace:

    main.19a42d39f5b3e0ecd0d5.js:1 TypeError: Cannot read property 'purpose' of null
        at n._updateMapFrameData (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n._onLayerDataLoaded (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at Array.<anonymous> (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n.invoke (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n._invokePendingEvents (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n.completeDataLoadingPhase (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n._onDataMonitorDataLoaded (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at Array.<anonymous> (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n.invoke (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n._onDataLoaderCompleted (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1
        at Array.<anonymous> (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n.invoke (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n._onDataSourceResponse (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1
        at t.getPrimitives (322a9035.js?bu=BIEF8QT1AYYF:1)
        at n._requestPrimitives (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n._loadOnDataSourceAvailability (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n.beginLoad (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n.setFrame (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n.setFrame (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n._setFrame (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n._failFrame (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at n._onFrameTimedOut (de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1)
        at de83dd5e.js?bu=B4EFrwWxBbMF8wS6BYYF:1
        at mapcontrol?callback=GetMap&key=<Key Removed>
        at e.invokeTask (polyfills.08556bf02b01d59292e7.js:1)
        at Object.onInvokeTask (main.19a42d39f5b3e0ecd0d5.js:1)
        at e.invokeTask (polyfills.08556bf02b01d59292e7.js:1)
        at t.runTask (polyfills.08556bf02b01d59292e7.js:1)
        at t.invokeTask (polyfills.08556bf02b01d59292e7.js:1)
        at invoke (polyfills.08556bf02b01d59292e7.js:1)
        at n.args.<computed> (polyfills.08556bf02b01d59292e7.js:1)

    Rewards,

    Randy


    Friday, September 20, 2019 4:30 PM
  • When we see this error the Bing Maps API call requests data from the server (in this case a zoom event and focusing on a pin) takes place and then the JSONP Api is called:
    callback: f4c843

    General:
    Request URL: https://dev.virtualearth.net/REST/v1/Traffic/Incidents/36.597889133070204,-106.875,40.97989806962013,-101.25/?severity=4&key=AhLL71ivu_mTWuAvJ4WnOcj-4wSeNCc0iplsfyLXnipM-eHicofA7RjD5VV8WZj3&o=json&jsonp=Microsoft.Maps.NetworkCallbacks.f4c843
    Request Method: GET
    Status Code: 200  (from disk cache)
    Remote Address: 20.36.236.157:443
    Referrer Policy: no-referrer-when-downgrade
    access-control-allow-headers: Content-Type
    access-control-allow-methods: POST, GET, OPTIONS
    access-control-allow-origin: *
    cache-control: max-age=240
    content-encoding: gzip
    content-length: 1136
    content-type: application/x-javascript; charset=utf-8
    date: Fri, 20 Sep 2019 17:45:37 GMT
    server: Microsoft-IIS/10.0
    status: 200
    vary: Accept-Encoding
    x-aspnet-version: 4.0.30319
    x-bm-fe-elapsed: 143
    x-bm-srv: BN000021E6, BN200003AA
    x-bm-traceid: 1bb3ef95f481493884a5d83028af6ac1
    x-ms-bm-ws-info: 0
    x-powered-by: ASP.NET, ARR/3.0, ASP.NET
    Provisional headers are shown
    Referer: https://wfxqa.csgedirect.com/ent/msokairos/fsm/support-tasks/tasks/performance
    Sec-Fetch-Mode: no-cors
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
    severity: 4
    key: <key removed>
    o: json
    jsonp: Microsoft.Maps.NetworkCallbacks.f4c843
    And then the stacktrace above (with the appropriate callback function name) takes place.

    Friday, September 20, 2019 5:50 PM
  • Hi Randy,

    I have removed your private Keys from this public forum but at this point I would very much recommend that you work the rest of this issue with our support team directly to avoid further sharing of your key.  This can be done by going to Microsoft.com/maps, choosing Support then Enterprise Customer Support and creating your case. 

    Stephen Dubien is a member of that team and can work with you directly.  While we do not support Angular specifically, we do support the V8 control and will do our best to get you past this issue.  In other frameworks when expected values are null or don't exist it is usually because the request for that item comes before the control is loaded but again, I can't say for sure that is what is happening in this case.

    We will be watching the Bing Maps Support queue for your case and you can refer to this post in your case details.

    Sincerely,

    IoTGirl

    Sunday, September 22, 2019 6:14 AM
    Owner