DEV Community

Guilherme-Bodart
Guilherme-Bodart

Posted on

Using Google Maps Natively in Angular 20 β€” Modern vs Traditional Approach

πŸš€ 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
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ 2. Install Google Maps Packages

npm install @angular/google-maps
npm install @googlemaps/markerclusterer
Enter fullscreen mode Exit fullscreen mode

πŸ”‘ 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>
Enter fullscreen mode Exit fullscreen mode

πŸ—ΊοΈ 5. Generate the Map Component

ng g c map-new
ng g c map-old
Enter fullscreen mode Exit fullscreen mode

🧭 6. Add Routes

export const routes: Rotas = [
  { path: '', component: MapComponent },
  { path: 'map', component: MapGoogleComponente },
];
Enter fullscreen mode Exit fullscreen mode

🌍 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
      });
    }
  }

Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

🧩 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;
}

Enter fullscreen mode Exit fullscreen mode

map-google.html

<div #mapContainer id="map" style="width: 100vw; height: 100vh;"></div>

Enter fullscreen mode Exit fullscreen mode

πŸ”₯ 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?

Comparison Table

🧠 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)

Marker Clustering with Google Maps

Google Map with Pokemon

Top comments (0)