Angular google map, timing issue when reloading component? How to refactor for reusability?

Discussão em 'Angular' iniciado por Christian, Fevereiro 20, 2025.

  1. Christian

    Christian Guest

    I work with angular 19 and "@angular/google-maps": "^19.1.2".

    I currently have a map component used in a specific usecase which uses a map with advanced markers, the clusterer and places search. This works for now, however I want to use a google map in quite a few places of my spa.

    I am a bit confused about the maps api loading, timings and of course find a lot of examples about the java script api and much less advanced examples using the angular component.

    As of now I am working with a map component like this:

    Template:

    <div style="width: 100%; height: 100%">
    <input id="pac-input" #placesContainer style="margin-top: 10px" type="text"/>
    <google-map (mapInitialized)="onMapReady($event)" mapId="myMap" [zoom]="zoom"
    ="center"
    width="100%" height="100%">

    <map-marker-clusterer>
    @for (project of projectMarkers; track project.position) {
    <map-advanced-marker (mapClick)="onMarkerClick(project.projectId)"
    [position]="project.position"
    [content]="project.icon"
    />
    }
    </map-marker-clusterer>
    </google-map>
    </div>


    Component:

    export class MapComponent implements OnInit, AfterViewInit
    {
    @Input() zoom: number = 7;
    @Input() center: google.maps.LatLngLiteral = {lat: 51.1657, lng: 10.4515};
    @Input() projects: ProjectsOverviewDto[] = [];
    @Output() markerClicked = new EventEmitter<string | null>();

    projectMarkers: ProjectMarker[] = [];
    currentExpandedProjectId: string | null = null;

    markerSelectedSvgString = '<svg ...</svg>';
    markerUnselectedSvgString = '<svg ....</svg>';

    @ViewChild('placesContainer') placesElement!: ElementRef;
    private map!: google.maps.Map;

    constructor(private mapService: MapBaseService) {}

    ngOnInit(): void {
    this.setProjectMarkers()
    }

    async ngAfterViewInit(): Promise<void> {
    if (this.map) {
    await this.mapService.initPlacesSearch(this.map, this.placesElement);
    }
    }

    async onMapReady(map: google.maps.Map): Promise<void> {
    this.map = map;
    this.setBounds()
    this.map.setOptions({
    gestureHandling: 'cooperative',
    fullscreenControl: false,
    mapTypeControl: true,
    mapTypeControlOptions: {
    style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
    position: google.maps.ControlPosition.TOP_RIGHT
    }
    });
    await this.mapService.initPlacesSearch(this.map, this.placesElement);
    }


    MapService:

    export class MapBaseService {

    constructor() {}

    async initPlacesSearch(map: google.maps.Map, placesElement: ElementRef<HTMLInputElement>) {

    if (!placesElement?.nativeElement) {
    console.error("Places element not found");
    return;
    }

    await google.maps.importLibrary("places");
    let searchBox = new google.maps.places.SearchBox(placesElement.nativeElement);

    placesElement.nativeElement.placeholder = "Search location...";

    if (!map.controls[google.maps.ControlPosition.TOP_LEFT].getArray().includes(placesElement.nativeElement)) {
    map.controls[google.maps.ControlPosition.TOP_LEFT].push(placesElement.nativeElement);
    }

    // Bias the search box towards the current map viewport
    map.addListener("bounds_changed", () => {
    searchBox.setBounds(map.getBounds() as google.maps.LatLngBounds);
    });

    searchBox.addListener("places_changed", () => {
    const places = searchBox.getPlaces();
    if (!places || places.length === 0) return;

    const firstPlace = places[0];
    const bounds = new google.maps.LatLngBounds();

    if (firstPlace?.geometry) {
    if (firstPlace.geometry.viewport) {
    bounds.union(firstPlace.geometry.viewport);
    } else if (firstPlace.geometry.location) {
    bounds.extend(firstPlace.geometry.location);
    }
    }
    map.fitBounds(bounds);
    });
    }
    }


    I am trying to move logic like e.g. the places search init into a service since I will probably have multiple map components in my application and I want to share some code.

    However, I previously tried to do it without also calling the initPlacesSearch in a AfterViewInit, only in the onMapReadyfunction. This worked for the initial load of the component. When navigating to another component and back I got a TypeError: Cannot read properties of undefined (reading 'nativeElement') error.

    This solution seems a bit hacky and I am convinced that it can be done cleaner or more efficient. So any tips regarding reorganizing the map component and service is much appreciated. As I said, the goal is to use maps for different usecases like visualizing locations, using it to define areas etc.

    Continue reading...​

Compartilhe esta Página