AngularJS ng-include inside of Google Maps InfoWindow?
Solution 1
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.
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.
Solution 2
The above answers are solid, but you lose your binding on:
infowindow.setContent( compiled[0].innerHTML );
Do this instead:
infowindow.setContent( compiled[0] );
Otherwise if you have something like <div>{{myVar}}</div>
, it won't update if myVar
is updated in your app.
Solution 3
Have you tried the compile function? http://docs.angularjs.org/api/ng.$compile
I did not look into angular a lot yet, but I think this could work.
alternatively you could try bootstrapping the stuff. but I dont believe it is the correct way... http://docs.angularjs.org/guide/bootstrap
Related videos on Youtube
Jakob Jingleheimer
Updated on September 15, 2022Comments
-
Jakob Jingleheimer over 1 year
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
-
theres over 10 years@jacob You probably shoud use
infowindow.setContent( compiled[0]);
instead ofinfowindow.setContent( compiled[0].innerHTML );
-
-
Jakob Jingleheimer over 11 yearsHi Luke, so I would do that and cache the 'compiled' output and inject it into the
.addListener()
function? -
Jakob Jingleheimer over 11 yearsHi Mark, that looks like it might work, thanks! btw, what additional code would you like to see? (for testing, the contents of
infowindow.html
is<h3>Foo</h3>
). I added a bit more to the code in my answer, but that's really all there is (nothing too special). -
Mark Rajcok over 11 years@jacob, where is the code you show? Is it in an Angular service, directive, controller? Since you are doing DOM manipulation, it should be in a directive. Also, it is not clear what element we can compile... i.e., what "foundElement" will be in my answer above. Well, I went and took a quick look at the Google APIs and (at least in the one example I saw), setContent() or open() (not sure which) created a div with
id="content"
, so you should be able to find that element and $compile it. -
Jakob Jingleheimer over 11 yearsThe map is initiated in a service (I added a plunk to the question). As far as I can see via Dev Tools, gMaps api does not by default create a div with
id="content"
. -
Jakob Jingleheimer over 11 yearsIf Angular isn't noticing the
ngInclude
as it is, why would it notice one that I create? Or are you saying this whole service should really be a directive? -
Mark Rajcok over 11 yearsI've been looking at this, but I'm not making much progress. A few notes: scope.$apply() must be called after $compile; addListener() is being passed
scope.markers[i]
instead ofscope
(?); yes, use infowindow_content (not content) to find the element to $compile; it doesn't have to be in a directive to work, it is just that normally DOM manipulations are performed by directives, not services. -
Jakob Jingleheimer over 11 yearsthanks for the update! Bad news tho: I the plunker doesn't work for me no matter how many times I click the marker :(
-
Jakob Jingleheimer over 11 yearsHazzah! (this was my first time using Plunker—I have no idea how it handles versioning/forking/branching). Thanks a tonne!
-
Jakob Jingleheimer over 11 yearsMark, I'm going to try moving the content stuff (
var content …
) outside of the event listener to see if that fixes the issue with first click (it appears you're right: first click triggers the compile and content isn't ready until the second click). -
John Doe about 11 years@MarkRajcok Hi! I had to use pretty much the same
compiled[0].innerHTML
, then I tried to find something similar and found only this answer. It looks like some kind of a hack to me. Is it an API problem? Why do we need to take the first element and then itsinnerHTML
? I'm learning Angular and maybe I just don't understand something, but this result of a compilation (an array) is a bit ugly. -
Mark Rajcok about 11 years@JohnDoe,
$compile(content)
returns a linking function.$compile(content)(scope)
(i.e., executing the linking function) returns a wrapped element -- wrapped in jQuery or jqLite.[0]
of that wrapped element returns the DOM element (i.e., unwrapped element). So, we $compile the HTML snippet (because it contains Angular directives), then we extract the innerHTML viacompiled[0].innerHTML
and set infowindow's content with that. -
John Doe almost 11 years@MarkRajcok Thanks, but that's not what I was talking about. I understood what happened here, but this
compiled[0].innerHTML
part looks simply unnatural. I'm not sure, maybe it's just a rare use case. Thanks anyway. -
Nick about 10 yearsWhat if you need more than the 0th element. How do you reduce / flatten the rest of the compiled elements into flat html for setContent? compiled[0-5].innerHTML
-
chrisboustead over 8 yearsThis is very important, as the above answers are useless for anything but use of ng-include.