CVL
CVL

Reputation: 11

Angular 15 Google Maps updating marker positions in real time

Using @angular/google-maps to create a real time map of public transport using transport for NSW openData.

I can successfully create the initial map, and put markers onto the map (in this example ferry locations) but updating the array of markers does not update the marker positions on the map.

It DOES, however, update data in the view when I output to text via an *ngFor loop in the component.

My question: how to update the marker positions in the Angular Google Map when the array of markers updates?

Angular 15 code:

livedata-map-content.component.html

<div class="live-transport-map">
    <div *ngIf="apiLoaded" class="google-maps-container">
        <google-map width="100%" height="100%" [options]="options">
              <map-marker  *ngFor="let marker of markers"
              [position]="marker.position"
              [label]="marker.label"
              [title]="marker.title"
              [options]="marker.options">
          </map-marker>
            
  <span *ngFor="let marker of markers">NAME:  {{marker.title}} LAT: {{marker.position.lat}} LNG: {{marker.position.lng}}!!!! </span>
        </google-map>
    </div>
</div>

livedata-map-content.component.ts

import { ChangeDetectorRef, Component, AfterViewInit } from '@angular/core';
import { MapService} from '../../../services/map.service';

@Component({
  selector: 'app-livedata-map-content',
  templateUrl: './livedata-map-content.component.html',
  styleUrls: ['./livedata-map-content.component.scss']
})
export class LivedataMapContentComponent implements AfterViewInit {
  
  public apiLoaded: any = false;
  public markers: Array<any> = [];
  public period = 10000;
  public iconStr = "http://maps.google.com/mapfiles/ms/icons/green-dot.png";

  public options: google.maps.MapOptions = {
    center: {lat: -33.853759, lng: 151.212803}, // Sydney Harbour center
    zoom: 14,
  };

  constructor(
      private mapService: MapService
    ) { }

  ngAfterViewInit(): void {
    
    // Load the maps API after view is init
    this.mapService.loadGoogleMapsAPI().subscribe(data => {
      
      this.apiLoaded = data;

      if(this.apiLoaded === true) {
        // Once it's loaded start getting live data
        setInterval(this.updateMarkers, this.period);
      }
    });

    // Initial marker setup on map
    this.mapService.getLivePositionData('ferry').subscribe(positionData => {

      let transportEntitiesArray = positionData.data.entity;

      transportEntitiesArray.forEach((transportEntity: any) => {

        this.markers.push({
          tripId: transportEntity.vehicle.trip.tripId,
          position: {
            lat: transportEntity.vehicle.position.latitude,
            lng: transportEntity.vehicle.position.longitude,
          },
          title: transportEntity.vehicle.vehicle.id + '\n' + transportEntity.vehicle.vehicle.label,
          options: {
            icon: this.iconStr,
          }
        });
      });

      this.cd.detectChanges();
    });
  }
    
  updateMarkers = () => {

    this.mapService.getLivePositionData('ferry').subscribe(positionData => {

      positionData.data.entity.forEach(positionDataItem => {
        
        this.markers.forEach(marker => {
          // Only update markers with new positions
          if(marker.tripId === positionDataItem.vehicle.trip.tripId && (marker.position.lat !== positionDataItem.vehicle.position.latitude || marker.position.lng !== positionDataItem.vehicle.position.longitude)){
            marker.position.lat = positionDataItem.vehicle.position.latitude;
            marker.position.lng = positionDataItem.vehicle.position.longitude;
          }
        });
      });

      this.cd.detectChanges();

    });
  }
}

map.service.ts

...

  public loadGoogleMapsAPI = () => {

    const mapsAPIURL = `https://maps.googleapis.com/maps/api/js?key=${environment.tempGoogleMapsID}`
    console.log('mapsAPIURL ',mapsAPIURL);

    return this.http.jsonp<any>(mapsAPIURL, 'callback').pipe(
      map((data) => {
        console.log('DATA ',data);
        return true}),
      catchError((error) => {
        console.log('error ',error);
        return of(false)}),
    );

  }

...

  getLivePositionData = (transportMode: any) => {

    const getLivePositionDataObject = {
      transportMode: transportMode
    }
    
    const getLivePositionDataDataURL = this.openDataAPI + 'positiondata';

    return this.http.post<any>(getLivePositionDataDataURL, getLivePositionDataObject);
  }
...

This draws the map and populates the array as expected, but does not update marker positions on the map.

IMAGE: the Angular 15 Material Google Maps with ferry position markers working

Upvotes: 1

Views: 1888

Answers (2)

Jan Dolejsi
Jan Dolejsi

Reputation: 1528

I ran into the same. Couldn't get the updates to the marker objects reflected by the map via the bindings and change detection. Then read more about the Angular change detection and how it works better if objects we bind to are immutable. The default change detection strategy checks if the object reference is the same, or it is a different object.

So rather than changing the marker.position.lat, you should make a deep copy of the object. Either by the object spread operator (but note that it is a shallow copy), or by creating a new instance of the marker object.

Also, if you are binding to markers as a field of type Array, the reference of the object is the same, unless you assign a new deep copy of the list to that field. I am not expert, but I see other people adding an Observable<Marker[]> field (or even better a Subject<Marker[]>) and explicitly sending the update once the list is updated.

The example in the Angular change detection article shows this pattern in full.

Upvotes: 0

Christophe Chenel
Christophe Chenel

Reputation: 1921

I have the same issue than you. I found a way to update the first marker in the html.

In the component add a ViewChild:

    @ViewChild(MapMarker, { static: false }) myMarker!: MapMarker;

Then you will be able to update the marker like this :

this.myMarker?.marker?.setPosition({lat: 0, lng: 0});

Still, it is a workaround but I am unable to select a specific marker and update it.

Upvotes: 0

Related Questions