AngularJS ng-include inside of Google Maps InfoWindow?

Jakob Jingleheimer picture Jakob Jingleheimer · Jan 9, 2013 · Viewed 17.4k times · Source

I'm trying to include a template file views/infowindow.html as the content of my InfoWindow from service I wrote to initiate the google maps api:

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

  var latLng  = locations[i],
    marker = new google.maps.Marker({
      …
    }),
    infowindow = new google.maps.InfoWindow();

  google.maps.event.addListener(
    marker,
    'click',
    (function( marker , latLng ){
      return function(){
        var content = '<div ng-include src="\'infowindow.html\'"></div>';
        infowindow.setContent( content );
        infowindow.open( Map , marker );
      }//return fn()
    })( marker , latLng )
  );//addListener

}//for

However, it seems that Angular is not processing content when it is inserted into the InfoWindow (when inspecting the code via Dev Tools, the code that gets inserted is <div ng-include src="'views/infowindow.html'"></div>).

I was hoping Angular would pre-process my include before it was inserted into the InfoWindow, but alas no.

Is what I'm trying to do possible?

I'm thinking that I'll have to somehow cache the template before passing it to infowindow.setContent(), but I don't know how to do that (or if that's even what I should be doing). I would prefer to load the template on the event instead of caching and injecting it for each marker.

EDIT Looking at $templateCache and a related SO question.

EDIT 2 Here's a plunk that tries to use $compile (the content of InfoWindow is still <div id="infowindow_content" ng-include src="'infowindow.html'"></div>)


SOLUTION

The basis for this came from Mark's answer below. In his solution, the content for InfoWindow is compiled on first click (of any marker) but the InfoWindow does not actually open until another click on any Marker, probably because GoogleMaps is impatient.

Moving the $compile outside and then passing the compiled template into .addListener solves this problem:

for ( … ) {
  …
  infowindow = new google.maps.InfoWindow();
  scope.markers …
  var content = '<div id="infowindow_content" ng-include src="\'infowindow.html\'"></div>';
  var compiled = $compile(content)(scope);

  google.maps.event.addListener(
    marker,
    'click',
    (function( marker , scope, compiled , localLatLng ){
      return function(){
        scope.latLng = localLatLng;//to make data available to template
        scope.$apply();//must be inside write new values for each marker
        infowindow.setContent( compiled[0].innerHTML );
        infowindow.open( Map , marker );
      };//return fn()
    })( marker , scope, compiled , scope.markers[i].locations )
  );//addListener

}//for

Updated Plunker.

Answer

Mark Rajcok picture Mark Rajcok · Jan 9, 2013

After you add the content to the DOM, you'll need to find it (maybe with a jQquery selector?), then $compile() it and apply it to the appropriate scope. This will cause Angular to parse your content and act on any directives it finds (like ng-include).

E.g., $compile(foundElement)(scope)

Without more code, it is difficult to give a more precise answer. However, here is a similar question and answer you can look at.

Update: okay, I finally got this to work, and I learned a few things.

google.maps.event.addListener(
      marker,
      'click',
      (function( marker , scope, localLatLng ){
        return function(){
          var content = '<div id="infowindow_content" ng-include src="\'infowindow.html\'"></div>';
          scope.latLng = localLatLng;
          var compiled = $compile(content)(scope);
          scope.$apply();
          infowindow.setContent( compiled[0].innerHTML );
          infowindow.open( Map , marker );
        };//return fn()
      })( marker , scope, scope.markers[i].locations )

I was under the impression that only DOM elements could be $compiled -- i.e., that I first had to add the content to the DOM, and then compile it. It turns out that is not true. Above, I first compile content against the scope, and then add it to the DOM. (I don't know if this might break databinding -- i.e., the $watch()es that were set up by $compile.) I had to set scope.latLng because the ng-included template needs to interpolate {{latLng[0]}} and {{latLng[1]}}. I used innerHTML instead of outerHTML so that only the contents of infowindow.html are inserted.

Plunker.

Update2: Clicking does not work the first time. It appears that 'infowindow.html' is not loaded until a second click (I tried calling scope.$apply() a second time... didn't help). When I had the plunker working, I had inlined the contents of infowindow.html in index.html:

<script type="text/ng-template" id="/test.html">
  <h4>{{latLng[0]}},{{latLng[1]}}</h4>
</script>

I was using that in addListener():

var content = '<div id="infowindow_content" ng-include src="\'/test.html\'"></div>';

I changed the plunker to use the inlined template.