Jason Simpson
Jason Simpson

Reputation: 4952

How to show dynamic content in @angular/google-maps infoWindow

Specifically, how do you show information related to the marker in the respective infoWindow?

By following the example on the repo here, the same information is opened for every marker. With the old angular google maps you could easily just insert the info window inside the marker with whatever data you wanted.

I saw a few examples of setting the content with {{ contentHere }} in the template, but that only allows for strings. I need to use all the angular template stuff like usual.

Let's say our template looks something like the repo example:

<google-map height="400px"
        width="750px"
        [center]="center"
        [zoom]="zoom">
  <map-marker #marker
          *ngFor="let place of places"
          [position]="{ lat: place.latitude, lng: place.longitude }"
          [options]="markerOptions"
          (mapClick)="openInfoWindow(marker)">

      <map-info-window>
        <-- this is what i'm concerned with -->
        {{ place.description }}
      </map-info-window>
  </map-marker>
</google-map>

And then we add viewchild like the repo example and open it in the same way:

@ViewChild(MapInfoWindow, {static: false}) infoWindow: MapInfoWindow;

openInfoWindow(marker: MapMarker) {
  this.infoWindow.open(marker);
}

Although this does open the correct marker and the marker will reflect dynamic info, this will show the same content / place.description for every info window, which isn't really surprising since a single ViewChild is being used. I've come up with all kinds of convoluted ways to do this and had a look through the source code but there doesn't seem to be an obvious and/or built in solution for displaying content in the window that is related to what is being used by the marker. It just seems weird to not have that as a basic example.

How would any of you go about doing this?

Upvotes: 13

Views: 16350

Answers (8)

Giovannisc
Giovannisc

Reputation: 47

I share with you my solution in @angular/[email protected]. / Les comparto mi solución en @angular/[email protected], espero pueda ser de utilidad para alguien si es el caso

check this souce: https://github.com/angular/components/blob/16.2.x/src/google-maps/README.md

remember chech your Angular version branch

@angular/[email protected]

import { Component, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { iglesiasAsociadas } from './shared/data/iglesiasasociadas.data';
import {MapInfoWindow, MapMarker} from '@angular/google-maps';
import { PointMap } from './shared/model/pointmap';

@Component({
  selector: 'app-mapadev',
  templateUrl: './mapadev.component.html',
  styleUrls: ['./mapadev.component.css']
})
export class MapadevComponent {
  @ViewChild(MapInfoWindow, { static: false }) infoWindow!: MapInfoWindow;
  @ViewChildren(MapMarker) markers!: QueryList<MapMarker>;

  pints: PointMap[] = [];
  infoContent: string = "";
  apiLoaded: Observable<boolean>;
  constructor(httpClient: HttpClient) {
    this.apiLoaded = httpClient.jsonp('https://maps.googleapis.com/maps/api/js?key=YOUR_APIkEY', 'callback')//main
        .pipe(
          map(() => true),
          catchError(() => of(false)),
        );
  }
  options: google.maps.MapOptions = {
    center: {lat: 40, lng: -80},
    zoom: 4
  };
  center: google.maps.LatLngLiteral = {lat: 40, lng: -20};
  zoom = 2;
  markerOptions: google.maps.MarkerOptions = {draggable: false};
  ngOnInit(): void {
    
  }
  
  markerPositions: google.maps.LatLngLiteral[] = iglesiasAsociadas.map(data=>{
    return {lat: data.location[0], lng: data.location[1]}
  });
  points: PointMap[] = iglesiasAsociadas.map(data=>{
    return { 
      title: data.title,
      position:{lat: data.location[0], lng: data.location[1]},
      info: 
      `<div>
        <H1>${data.title}</H1>
        <p>Dirección: ${data.direccion}</p>
        <p>Liderazgo: ${data.liderazgo}</p>
        <p>Movil: ${data.movil}</p>
        <p>Email: ${data.email}</p>
        <p>Webpage: ${data.webpage}</p>
      </div>`
    }
  });
  addMarker(event: google.maps.MapMouseEvent ) {
    if ((event.latLng!=null) && !(event==null))this.markerPositions.push(event.latLng.toJSON());
  }

  openInfoWindow(marker: MapMarker, title: string, markerInfo: string) {
    let positonsMarker = marker.getPosition()?.toString();
    console.log(positonsMarker?.split(", "))
    console.log(marker)
    console.log(title, markerInfo);
    this.infoContent = markerInfo;
    this.infoWindow.open(marker);
  };
}
// I did make in other file and import in this file to used it. // Yo cree el modelo en otro archivo para poder utilizarlo aquí
`// export  class PointMap {
//     title: string="";
//     position: any; // {lat, lng} object - in accordance to the API
//     info: string=""; // HTML-formatted on server side in my case, but you may construct the content here from a number of plain string data
// }`
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.2/angular.min.js"></script>
<p>mapadev works!</p>
<div *ngIf="apiLoaded | async">
    <google-map 
    height="400px"
    width="750px"
    [center]="center"
    [zoom]="zoom"
        >
        <map-marker 
        #marker="mapMarker"
        *ngFor="let point of points"
        [position]="point.position"
        [title]="point.title"
        [options]="markerOptions"
        (mapClick)="openInfoWindow(marker, point.title, point.info)"
            ></map-marker>

        <map-info-window [innerHTML]="infoContent" > Info Window content</map-info-window>
    </google-map>
</div>

Upvotes: 0

Estefanadia
Estefanadia

Reputation: 1

Acept two arguments on openWindow function, I define a property as MarkerLabel in my Marcador interface in order to put a label, but you can set as string with the content and a property as LatLngLiteral to use to create the marker. Set the content using the infowindow View Child before open.

openInfoWindow method:

openInfoWindow(marker: MapMarker,markerPosition:Marcador) {
  
    this.infoWindow.infoWindow?.setContent(`${markerPosition.label.text}`)
    this.infoWindow.open(marker);
  }

Map Marker:

<map-marker #marker="mapMarker" *ngFor="let markerPosition of marcadores"
            [position]="markerPosition.position"
            (mapClick)="openInfoWindow(marker,markerPosition)"
           ></map-marker>
           <map-info-window></map-info-window


    export interface Marcador{
    label: google.maps.MarkerLabel,
    position: google.maps.LatLngLiteral
}

Upvotes: 0

Wojciech Marciniak
Wojciech Marciniak

Reputation: 325

Most answers here are incorrect. Either

  1. They're buggy by not passing type-checking (hashed ids point to HTML elements, not of MapMarker type, furthermore they are not unique as included in *ngFor loop) or
  2. Have bad impact on performance (you don't need as many InfoWindows as MapMarkers, one is enough).

What I like most is the solution Warren has proposed. My concept is similar and as simple as that:

TS

@ViewChild(MapInfoWindow, { static: false }) infoWindow: MapInfoWindow;
@ViewChildren(MapMarker) markers: QueryList<MapMarker>;
points: PointMap[] = [];
infoContent: string = "";
options = {
    scrollwheel: false,
    disableDoubleClickZoom: true,
    maxZoom: 15,
    minZoom: 6,
};

ngOnInit() {
    // points: PointMap[] array is filled up here
}

openInfoWindow(windowIndex: number, content: string) {
    this.markers.forEach((marker: MapMarker, ix: number) => {
        if (windowIndex === ix) {
            this.infoContent = content;
            this.infoWindow.open(marker);
        }
    });
}

My PointMap type (an array or list of these should come from the server side):

export class PointMap {
    title: string;
    position: any; // {lat, lng} object - in accordance to the API
    info: string; // HTML-formatted on server side in my case, but you may construct the content here from a number of plain string data
}

HTML:

<google-map [center]="center" width="100%" height="500px" [options]="options">
    <map-marker *ngFor="let marker of points; let ix = index
        [position]="marker.position" [title]="marker.title" 
        (mapClick)="openInfoWindow(ix, marker.info)">
    </map-marker>
    <map-info-window [innerHTML]="infoContent"></map-info-window>
</google-map>

Believe me, this does work.

Upvotes: 7

Warren Dowey
Warren Dowey

Reputation: 21

there is a really easy way to sort this, looking at this call:

 (mapClick)="openInfoWindow(marker)"

can be updated to pass a variable, referene or other details possibly even the content i.e.

 (mapClick)="openInfoWindow(marker, reference || object || content)"

i.e. code I use regularly:

<google-map #map
                    height="90%"
                    width="100%"
                    [zoom]="zoom"
                    [center]="center"
                    [options]="options"
                    (boundsChanged)="mapBoundsChanged()"
        >
            <map-marker
                #marker="mapMarker"
                *ngFor="let location of filteredLocations"
                [position]="{lat:location.locationLatitude,lng:location.locationLongitude }"
                [title]="location.locationName"
                [icon]="env.assetsUrlBase+locationTypeMap.get(location.locationTypeId)"
                (mapClick)="openInfo(marker, location)"
            >
            </map-marker>
            <map-info-window [innerHTML]="infoContent" #infoWindow="mapInfoWindow"></map-info-window>
        </google-map>

This way you do not need to repeat the map or info windows and only the markers.

I would also advise that you id your children and call them in your component.ts by the id ensuring that the id in the html states their type.

This also allows for multiple maps on a single view that can behave independently.

i.e.

@ViewChild('map', { static: true }) map: GoogleMap;
  @ViewChild('infoWindow', { static: true }) infoWindow: MapInfoWindow
  @ViewChild('businessMap', { static: true }) businessMap: GoogleMap;
  @ViewChild('businessInfoWindow', { static: true }) businessInfoWindow: MapInfoWindow

Upvotes: 2

Herv&#233;
Herv&#233;

Reputation: 3

Angular, doesn't want to display HTML code in a binding because, basically, it's a string binding (and for security reasons). But, instead of :

<map-info-window>
        <-- this is what i'm concerned with -->
        {{ place.description }}
</map-info-window>

You can simply do :

<map-info-window [innerHTML="place.description"]>
</map-info-window>

However, I didn't find a way to style that (even inline CSS doesn't seem to work). But basic formatting with <table> works fine.

Upvotes: 0

Jason Simpson
Jason Simpson

Reputation: 4952

Use ViewChildren, use index in the for loop in the template, pass in the index when marker is clicked, loop through all ViewChildren using an index inside of a forEach (for loop with index will not open the window for some reason), then if the index of the loop matches the index passed in, open that window. It seems like there would be a better way than this but it's all I could come up with after trying many things:

In component:

@ViewChildren(MapInfoWindow) infoWindowsView: QueryList<MapInfoWindow>;

openInfoWindow(marker: MapMarker, windowIndex: number) {
  /// stores the current index in forEach
  let curIdx = 0;
  this.infoWindowsView.forEach((window: MapInfoWindow) => {
    if (windowIndex === curIdx) {
      window.open(marker);
      curIdx++;
    } else {
      curIdx++;
    }
  });
}

In template:

<google-map height="400px"
    width="750px"
    [center]="center"
    [zoom]="zoom">
  <map-marker #marker
      *ngFor="let place of places; let i = index" <-- added index -->
      [position]="{ lat: place.latitude, lng: place.longitude }"
      [options]="markerOptions"
      (mapClick)="openInfoWindow(marker, i)"> <-- pass in index -->

      <map-info-window>
        {{ place.description }}
      </map-info-window>
  </map-marker>
</google-map>

This will open the info window with its respective marker.

Upvotes: 14

Fernando Cortese
Fernando Cortese

Reputation: 131

Simplest way, hope it can help

TS

public openInfoWindow(marker: MapMarker, infoWindow: MapInfoWindow) {
    infoWindow.open(marker);
}

HTML

<google-map *ngIf="mapOptions.center" width="100%" height="100%" [options]="mapOptions">
    <ng-container *ngFor="let elem of elements">
        <map-marker #marker="mapMarker" [position]="elem.position" [options]="elem.markerOptions" (mapClick)="openInfoWindow(marker, infoWindow)"></map-marker>
        <map-info-window #infoWindow="mapInfoWindow"> Content of {{ elem.id }} ...</map-info-window>
    </ng-container>
</google-map>

Upvotes: 13

King Leon
King Leon

Reputation: 1182

Another method is to modify the openInfoWindow function to take a string parameter, and use this string to display in the map-info-window.

In the component file.

export class AppComponent implements OnInit {
  @ViewChild(MapInfoWindow, { static: false }) infoWindow: MapInfoWindow;

  infoContent: string;

  openInfo(marker: MapMarker, content: string) {
    this.infoContent = content;
    this.infoWindow.open(marker);
  }
}

In the template.

<google-map
  height="500px"
  width="100%"
  [zoom]="zoom"
  [center]="center"
  [options]="options"
  (mapClick)="click($event)"
>
  <map-marker
    #markerElem
    *ngFor="let marker of markers"
    [position]="marker.position"
    [label]="marker.label"
    [title]="marker.title"
    [options]="marker.options"
    (mapClick)="openInfo(markerElem, marker.info)"
  >
  </map-marker>

  <map-info-window>{{ infoContent }}</map-info-window>
</google-map>

Upvotes: 1

Related Questions