4

Google map is integrated the javascript way and I want to call a angular2 function inside an infowindow like the following code. Take a look at infoContent for the button.

for (i = 0; i < locations.length; i++) {

  let locLatLng = new google.maps.LatLng(locations[i].latitude, locations[i].longitude);
  let infoContent = '<button (click)="myFunction(' + locations[i].id + ')">Details ...</button>';

  marker = new google.maps.Marker({
    position: locLatLng,
    map: this.map
  });

  google.maps.event.addListener(marker, 'click', (function(marker, i) {
    return function() {
      infowindow.setContent(infoContent);
      infowindow.open(this.map, marker);
    };
  })(marker, i));

}

Unfortunately the angular click event (click)="myFunction()" can't do it. There must be an other way. I would be very pleased if someone can point me to the right direction. Thanks in advance.

Alex
  • 927
  • 1
  • 12
  • 18
  • What's the problem with your `addListener()`? If `this.map` is not working as expected see my answer below. Is there something else? Some error message, ...? – Günter Zöchbauer Sep 28 '16 at 06:28
  • Opening the infowindow works fine. If I change the way from `(click)` to `onclick` I get an `Uncaught ReferenceError: myFunction is not defined`, but myFunction() is inside an angular component. – Alex Sep 28 '16 at 06:38
  • I guess you should use event delegation. You can also take a look at this question http://stackoverflow.com/questions/6378007/adding-event-to-element-inside-google-maps-api-infowindow – yurzui Sep 28 '16 at 06:54

5 Answers5

12

You can do the trick instancing a reference to ngZone in root Window and call it from inside the infoWindow.

First you need to get access to NgZone in constructor:

 constructor(public _ngZone: NgZone) {    }

And then you can set a pointer back to your zone in window, on ngOnInit, constructor or somewhere, depending of your code:

    window["angularComponentRef"] = { component: this, zone: this._ngZone };

Finally you can callback from infoWindow to your Angular function with zone.run like this:

    for (i = 0; i < locations.length; i++) {
        let locLatLng = new google.maps.LatLng(locations[i].latitude, locations[i].longitude);
        let infoContent: string = '<button onclick="window.angularComponentRef.zone.run(() => {window.angularComponentRef.component.myFunction(\'' + locations[i].id + '\');})">Details ...</button>';

        marker = new google.maps.Marker({
            position: locLatLng,
            map: this.map
        });

        google.maps.event.addListener(marker, 'click', (function (marker, i) {
            return function () {
                infowindow.setContent(infoContent);
                infowindow.open(this.map, marker);
            };
        })(marker, i));

    }

And you can clean the function when needed to avoid polute the window namespace, on ngOnDestroy for example:

 window["angularComponentRef"] = null;
David Gallardo
  • 454
  • 3
  • 9
3

update (from comment)

You can do it with ElementRef and Renderer but that is not the problem. The problem is to get a reference (direct DOM element reference or ElementRef) of the button. You can inject private elRef:ElementRef and use this.elRef.nativeElement.querySelector('infowindow button') or similar but if you access elRef.nativeElement you're out of the realm of platform neutrality again because nativeElement should not be accessed directly, but with the limitation of the Renderer of only being able to call methods but never to get a result in return, there is only so much you can do.

I would use a method like shown in Trigger event with infoWindow or InfoBox on click Google Map API V3 and then check the event.target if an element you are interested in was clicked.

original

If you want to access this.map in the event handler you should use arrow functions instead

  google.maps.event.addListener(marker, 'click', ((marker, i) => { // <<<===
    return () => { // <<<===
      infowindow.setContent(infoContent);
      infowindow.open(this.map, marker);
    };
  })(marker, i)

otherwise this. won't point to the current class instance

Community
  • 1
  • 1
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks for your reply. Unfortunately this don't solve my problem. I tried the js way with `onclick` instead of `(click)` but I only receive a `Uncaught ReferenceError: myFunction is not defined`. – Alex Sep 28 '16 at 06:36
  • 1
    I missed ` let infoContent = '';`. Angular2 doesn't process bindings added dynamically therefore this is not supposed to work. Only bindings added to component templates statically are processed by Angular2. You need to use `addEventListener()` for dynamically added elements. – Günter Zöchbauer Sep 28 '16 at 06:40
  • Thank you for pointing in the right direction. The angular2 way of addEventListener() seems to go throught `ElementRef` and `Renderer`. I try it out. – Alex Sep 28 '16 at 06:48
  • Yes, using `Renderer` and `ElementRef` is the platform-neutral way but if you use GoogleMaps you are outside this realm already anyway therefore it doesn't buy you much here. See http://stackoverflow.com/questions/12102598/trigger-event-with-infowindow-or-infobox-on-click-google-map-api-v3 for an example. Because `setContent()` takes a string, there is no way to add event handlers to individual elements. I assume you can get the actual element from the `MouseEvent` you receive from `click` events on the infowindow itself. – Günter Zöchbauer Sep 28 '16 at 06:55
  • Yes, I know the answer above, but there must be a way to bind from outside the google map script. First I try it with ElementRef and Renderer. Let's see if this works. – Alex Sep 28 '16 at 07:00
  • 1
    You can do it with `ElementRef` and `Renderer` but that is not the problem. The problem is to get a reference (direct DOM element reference or `ElementRef`) of the button. You can inject `private elRef:ElementRef` and use `this.elRef.nativeElement.querySelector('infowindow button')` or similar but if you access `elRef.nativeElement` you're out of the realm of platform neutrality again because `nativeElement` should not be accessed directly but with the limitation of the `Renderer` of only being able to call methods but never to get a result in return there is only so much you can do. – Günter Zöchbauer Sep 28 '16 at 07:03
  • Muah. That seems to be difficult. Anyway. Thank you for your time. I have to go in lab :) – Alex Sep 28 '16 at 07:09
  • Maybe you want to rewrite your answer with the statement of your last comment. So I can mark it as right answer. Thanks. – Alex Sep 28 '16 at 07:59
2

Here is my solution. First, I set id to button in info window. After that, I caught 'domready' event for info window which means: all the HTML has been received and parsed by the browser into the DOM tree which can now be manipulated. And then with getElementById accessed to button and add event 'click'. Here you can call your function defined in ts file.

const infoWindowContent = '<h6>' + community.typeOfCommunity + '</h6>' +
          '<p>' + community.address.name + ' ' + community.streetNumber + ', ' + community.city + '</p>' +
          '<p>Manager: ' + community.manager.fullName + '</p>' +
          '<button  id="infoWindowButton">More info</button>';
        const infoWindow = new google.maps.InfoWindow({
          content: infoWindowContent
        });

        marker.addListener('click', () => {
          infoWindow.open(this.map, marker);
        });

        infoWindow.addListener('domready', () => {
          document.getElementById("infoWindowButton").addEventListener("click", () => {
            this.myFunction();
          });
        });
  • Using Angular 13 I copied the `infoWIndow.addListener...` block verbatim and it worked. Much easier than other solutions I found. – Rin and Len Mar 03 '22 at 11:16
0

Please try this complete solution for this question.

var infowindow = new google.maps.InfoWindow({});

    for (i = 0; i < locations.length; i++) {

        let locLatLng = new google.maps.LatLng(locations[i].latitude, locations[i].longitude);
        let infoContent = '<div style="padding: 0.5em;">' +
            '<h5 id="lt" style="font-size: 12px">'+locations[i].id +'</h5>'+
            '<button id="btn" style="font-size: 8px;" >Click</button>'+
            '</div>'

        marker = new google.maps.Marker({
            position: locLatLng,
            map: this.map
        });

        google.maps.event.addListener(marker, 'click', (function(marker, i) {
            return function() {
                infowindow.setContent(infoContent);
                infowindow.open(this.map, marker);
            };
        })(marker, i));
    }

    infowindow.addListener('domready', () => {
        var val = document.getElementById('lt').innerHTML;
        document.getElementById("btn").addEventListener("click", () => {
            this.myFunction(val);
        });
    });
YMA
  • 16
  • 6
-1

I'm search a lot and found the answer:

You must use: $compile(htmlString)(scope)

In your example try this:

var infoContent = '<button ng-click="myFunction(' + locations[i].id + ')">Details</button>';

var scope = angular.element(document.getElementById('anyElementId')).scope();
var compiled = $compile(htmlElement)(scope);

And then use:

infowindow.setContent(compiled[0]);

Wala!