π Using Google Maps Natively in Angular 20 β Modern vs Traditional Approach ( My first post, criticism is welcome )
Google Maps finally landed natively inside Angular 17+ through the @angular/google-maps package, and Angular 20 makes everything even smoother. But is it always better than the old approach using pure TypeScript, the JS API, and manual DOM manipulation?
In this post, weβll explore both approaches:
Example 1 β The NEW way: native Angular Google Maps using HTML components
Example 2 β The OLD way: building everything in TypeScript using the Maps JS SDK
You'll learn:
- How to start a project
- How to install Google Maps
- How to configure the API
- How to cluster markers
- How to render 5,000 animated PokΓ©mon markers
- How to add polygons
- Pros and cons of each approach
π§± 1. Create the Angular Project
ng new google-maps-demo --standalone
cd google-maps-demo
π¦ 2. Install Google Maps Packages
npm install @angular/google-maps
npm install @googlemaps/markerclusterer
π 3. Generate Your Google Maps API Key
Create your API key here:
π GOOGLE API KEY
Enable:
- Maps JavaScript API
- Maps Static API
π§© 4. Add Google Maps Scripts to index.html
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>
<script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"></script>
πΊοΈ 5. Generate the Map Component
ng g c map-new
ng g c map-old
π§ 6. Add Routes
export const routes: Rotas = [
{ path: '', component: MapComponent },
{ path: 'map', component: MapGoogleComponente },
];
π Example 1 β The NEW Native Angular Google Maps (HTML-Based)
This is the modern approach introduced in Angular 17+.
You can declare markers, polygons, clusters directly in the HTML template, using real Angular components.
β Advantages
- Super clean and Angular-friendly
- Template-driven
- Reactive updates
- Built-in Map + Marker + Polygon + Cluster components
- Works perfectly with SSR
β Disadvantages
- Still evolving and missing some lower-level features
- Less control than the raw JS API
- You depend on Angular's wrappers
π Full Component (HTML Version)
map.ts
// basic options for creating a map
options: google.maps.MapOptions = {
center: { lat: -20.20, lng: -40.45 },
zoom: 12,
mapId: 'd03011fdd3c3cf1e5a83529b' // this is a clean map, without markers
};
// you can create your mapId here: [Map Id Google](https://developers.google.com/maps/documentation/javascript/map-ids/get-map-id)
// Thats block prevents errors in SSR.
constructor(@Inject(PLATFORM_ID) platformId: Object) {
this.isBrowser = isPlatformBrowser(platformId);
if (this.isBrowser) {
this.renderer = this.createClusterRenderer();
this.generateRandomMarkers();
}
}
ngAfterViewInit() {
// Wait for the Google Map instance to be created
this.googeMap = this.mapComponent.googleMap!;
// Event: triggers when the map finishes moving or zooming ("idle")
this.googeMap.addListener('idle', () => {
this.handleIdleEvent();
});
// Event: triggers whenever the map bounds change (move/zoom)
this.googeMap.addListener('bounds_changed', () => {
this.handleBoundsChanged();
});
}
createClusterRenderer(): Renderer {
return {
render: ({ count, position }) => {
const color = count > 10 ? '#EE1515' : '#FF0000';
const displayText =
count >= 1000 ? (count / 1000).toFixed(1) + "k" : count;
const fontSize =
count >= 1000 ? 16 : 18;
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="50" height="50">
<!-- red top -->
<circle cx="50" cy="50" r="48" fill="${color}" stroke="#222" stroke-width="4"/>
<!-- row div -->
<line x1="2" y1="50" x2="98" y2="50" stroke="#222" stroke-width="4"/>
<!-- white bottom -->
<path d="M 2 50 A 48 48 0 0 0 98 50" fill="white"/>
<!-- center circle -->
<circle cx="50" cy="50" r="18" fill="white" stroke="#222" stroke-width="4"/>
<!-- center text -->
<text
x="50"
y="56"
text-anchor="middle"
font-family="Arial"
font-weight="bold"
font-size="${fontSize}"
fill="#222">
${displayText}
</text>
</svg>
`;
const svgUrl = "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(svg);
const content = document.createElement("img");
content.src = svgUrl;
content.style.width = "50px";
content.style.height = "50px";
return new google.maps.marker.AdvancedMarkerElement({
position,
content
});
}
};
}
getRandomPokemonUrl(): string {
const pokemonValues = Object.values(Pokemon);
const random = pokemonValues[Math.floor(Math.random() * pokemonValues.length)];
return `https://img.pokemondb.net/sprites/black-white/anim/normal/${random}.gif`;
}
createPokemonSvg(imageUrl: string): HTMLElement {
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60">
<image href="${imageUrl}" x="10" y="10" height="40" width="40" />
</svg>
`;
const parser = new DOMParser();
return parser.parseFromString(svg, "image/svg+xml").documentElement;
}
generateRandomMarkers() {
for (let i = 0; i < 5000; i++) {
const imageUrl = this.getRandomPokemonUrl();
const icon = this.createPokemonSvg(imageUrl);
this.markerPositions.push({
position: {
lat: -20.45 + Math.random() * (-20.25 + 20.45),
lng: -40.45 + Math.random() * (-40.25 + 40.45)
},
iconHTML: icon
});
}
}
map.html
<google-map height="100vh" width="100vw" [options]="options">
<map-marker-clusterer [renderer]="renderer">
@for (pokemon of markerPositions; track $index) {
<map-advanced-marker
[position]="pokemon.position"
[options]="{ content: pokemon.iconHTML }">
</map-advanced-marker>
}
</map-marker-clusterer>
<map-polygon [paths]="polygonPaths" [options]="polygonOptions">
</map-polygon>
</google-map>
π§© Example 2 β The OLD Google Maps JavaScript SDK (Everything in TS)
This is the classic way: you manually create the map and markers using the JavaScript Maps SDK and manipulate the map directly.
βοΈ Advantages
- Complete control of the Google Maps API
- Access to advanced features not yet supported by Angular wrappers
- You can integrate any third-party library easily
β Disadvantages
- More verbose
- More DOM manipulation
- Harder to maintain
- Change detection is manual
- Harder to use with SSR
π Full Component (TypeScript Version)
map-google.ts
initMap() {
this.map = new google.maps.Map(this.mapContainer.nativeElement, {
center: { lat: -20.20, lng: -40.45 },
zoom: 12,
mapId: "d03011fdd3c3cf1e5a83529b",
});
}
createCluster() {
this.clusterer = new MarkerClusterer({
map: this.map,
markers: this.markers,
algorithm: new SuperClusterAlgorithm({
maxZoom: 15,
radius: 60 // optional β you can adjust
}),
renderer: {
render: ({ count, position }) => {
// Create a render with count and position
}
}
});
}
attachEvents() {
this.map.addListener("idle", () => {
this.showVisibleMarkers();
});
this.map.addListener("bounds_changed", () => {
const zoom = this.map.getZoom();
if (zoom && zoom > 15) {
this.showVisibleMarkers();
}
});
}
// You can use showVisibleMarkers() because you already have full control over
// all marker instances and the clusterer instance. Since you store the markers
// in arrays (this.markers, this.visibleMarkers)and the clusterer is created
// manually, you can directly add or remove markers from the clusterer at any time.
showVisibleMarkers() {
const bounds = this.map.getBounds();
if (!bounds) return;
// 1 β calculate the currently visible markers
const newVisible = this.markers.filter(m => {
if (!m.position) return false;
return bounds.contains(m.position);
});
// 2 β create efficient lists for comparison
const prev = this.visibleMarkers;
// 3 β removed markers (they were visible before but are not anymore)
const removed = prev.filter(
old => !newVisible.includes(old)
);
// 4 β added markers (they were not visible before but are now)
const added = newVisible.filter(
curr => !prev.includes(curr)
);
// 5 β apply changes directly to the clusterer
if (removed.length > 0) {
this.clusterer.removeMarkers(removed);
}
if (added.length > 0) {
this.clusterer.addMarkers(added);
}
// 6 β update the list of visible markers
this.visibleMarkers = newVisible;
}
map-google.html
<div #mapContainer id="map" style="width: 100vw; height: 100vh;"></div>
π₯ Results: 5,000 Animated PokΓ©mon Markers + Clustering
Both approaches generate:
βοΈ 5,000 random animated PokΓ©mon GIF markers
βοΈ Custom PokΓ©ball SVG cluster icons
βοΈ Dynamic bounds detection
βοΈ SuperCluster algorithm
βοΈ Marker visibility updates
βοΈ Polygon rendering (HTML version only)
βοΈ Comparison Table β Which One Should You Use?
π§ Conclusion
Google Maps now integrates beautifully with Angular 20, and depending on your use case you can choose between:
β Native Angular Components (Recommended for most apps)
Cleaner, reactive, simpler, SSR-friendly.
Perfect for dashboards, admin panels, or apps that need maintainability.
βοΈ Raw Google Maps JS API
More powerful and flexible.
Perfect for complex GIS systems, map editors, and advanced visualizations.
)
π Advanced Tips & Extra Possibilities
Even with the examples above, you can do much more with Google Maps:
- Multiple polygons and polylines: Add as many shapes as needed, each with its own styling and interactivity.
- MVCArray for better organization: Group polygons, polylines, or markers into MVCArray objects. This allows you to show or hide an entire group without affecting other groups.
- Dynamic management with MVCArray: Create one or more MVCArray instances and control their visibility or contents dynamically for full control. You can push new objects, remove specific ones, or clear the entire array.
- Separation by type or purpose: Organize objects (markers, polygons, polylines) by type, category, or any logical grouping using separate MVCArrays. This helps manage complex maps efficiently.
- Interactive objects: Any clickable object can open an InfoWindow or display details when clicked.
By combining MVCArray groups and InfoWindows, you maintain high performance and clean organization even with thousands of markers or complex shapes.
π Further Reading / Useful Links
Google Maps JavaScript API β InfoWindows
Google Maps JavaScript API β Polygons and Polylines
Google Maps JavaScript API β MVCArray (Layers)


Top comments (0)