<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Guilherme-Bodart</title>
    <description>The latest articles on Forem by Guilherme-Bodart (@guilhermebodart).</description>
    <link>https://forem.com/guilhermebodart</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F570557%2F0460e4c4-15d4-4b65-9b31-90578c90d5be.jpg</url>
      <title>Forem: Guilherme-Bodart</title>
      <link>https://forem.com/guilhermebodart</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/guilhermebodart"/>
    <language>en</language>
    <item>
      <title>Using Google Maps Natively in Angular 20 — Modern vs Traditional Approach</title>
      <dc:creator>Guilherme-Bodart</dc:creator>
      <pubDate>Fri, 05 Dec 2025 19:11:02 +0000</pubDate>
      <link>https://forem.com/guilhermebodart/using-google-maps-natively-in-angular-20-modern-vs-traditional-approach-23p2</link>
      <guid>https://forem.com/guilhermebodart/using-google-maps-natively-in-angular-20-modern-vs-traditional-approach-23p2</guid>
      <description>&lt;h2&gt;
  
  
  🚀 Using Google Maps Natively in Angular 20 — Modern vs Traditional Approach ( My first post, criticism is welcome )
&lt;/h2&gt;

&lt;p&gt;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?&lt;/p&gt;

&lt;p&gt;In this post, we’ll explore both approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 1 — The NEW way: native Angular Google Maps using HTML components&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 2 — The OLD way: building everything in TypeScript using the Maps JS SDK&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  You'll learn:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How to start a project&lt;/li&gt;
&lt;li&gt;How to install Google Maps&lt;/li&gt;
&lt;li&gt;How to configure the API&lt;/li&gt;
&lt;li&gt;How to cluster markers&lt;/li&gt;
&lt;li&gt;How to render 5,000 animated Pokémon markers&lt;/li&gt;
&lt;li&gt;How to add polygons&lt;/li&gt;
&lt;li&gt;Pros and cons of each approach&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🧱 1. Create the Angular Project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ng new google-maps-demo --standalone
cd google-maps-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📦 2. Install Google Maps Packages
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @angular/google-maps
npm install @googlemaps/markerclusterer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔑 3. Generate Your Google Maps API Key
&lt;/h2&gt;

&lt;p&gt;Create your API key here:&lt;/p&gt;

&lt;p&gt;👉&lt;a href="https://console.cloud.google.com/google/maps-apis" rel="noopener noreferrer"&gt; GOOGLE API KEY &lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maps JavaScript API&lt;/li&gt;
&lt;li&gt;Maps Static API&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🧩 4. Add Google Maps Scripts to index.html
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🗺️ 5. Generate the Map Component
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ng g c map-new
ng g c map-old
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧭 6. Add Routes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const routes: Rotas = [
  { path: '', component: MapComponent },
  { path: 'map', component: MapGoogleComponente },
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🌍 Example 1 — The NEW Native Angular Google Maps (HTML-Based)
&lt;/h2&gt;

&lt;p&gt;This is the modern approach introduced in Angular 17+.&lt;br&gt;
You can declare markers, polygons, clusters directly in the HTML template, using real Angular components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✅ Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Super clean and Angular-friendly&lt;/li&gt;
&lt;li&gt;Template-driven&lt;/li&gt;
&lt;li&gt;Reactive updates&lt;/li&gt;
&lt;li&gt;Built-in Map + Marker + Polygon + Cluster components&lt;/li&gt;
&lt;li&gt;Works perfectly with SSR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;❌ Disadvantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Still evolving and missing some lower-level features&lt;/li&gt;
&lt;li&gt;Less control than the raw JS API&lt;/li&gt;
&lt;li&gt;You depend on Angular's wrappers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📌 Full Component  (HTML Version)&lt;/p&gt;
&lt;h2&gt;
  
  
  map.ts
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  // 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', () =&amp;gt; {
     this.handleIdleEvent();
    });

   // Event: triggers whenever the map bounds change (move/zoom)
   this.googeMap.addListener('bounds_changed', () =&amp;gt; {
     this.handleBoundsChanged();
   });


  }

  createClusterRenderer(): Renderer {
    return {
      render: ({ count, position }) =&amp;gt; {

        const color = count &amp;gt; 10 ? '#EE1515' : '#FF0000';

        const displayText =
          count &amp;gt;= 1000 ? (count / 1000).toFixed(1) + "k" : count;

        const fontSize =
          count &amp;gt;= 1000 ? 16 : 18;

        const svg = `
        &amp;lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="50" height="50"&amp;gt;

          &amp;lt;!-- red top --&amp;gt;
          &amp;lt;circle cx="50" cy="50" r="48" fill="${color}" stroke="#222" stroke-width="4"/&amp;gt;

          &amp;lt;!-- row div --&amp;gt;
          &amp;lt;line x1="2" y1="50" x2="98" y2="50" stroke="#222" stroke-width="4"/&amp;gt;

          &amp;lt;!-- white bottom --&amp;gt;
          &amp;lt;path d="M 2 50 A 48 48 0 0 0 98 50" fill="white"/&amp;gt;

          &amp;lt;!-- center circle  --&amp;gt;
          &amp;lt;circle cx="50" cy="50" r="18" fill="white" stroke="#222" stroke-width="4"/&amp;gt;

          &amp;lt;!-- center text --&amp;gt;
          &amp;lt;text
            x="50"
            y="56"
            text-anchor="middle"
            font-family="Arial"
            font-weight="bold"
            font-size="${fontSize}"
            fill="#222"&amp;gt;
              ${displayText}
          &amp;lt;/text&amp;gt;

        &amp;lt;/svg&amp;gt;
      `;

        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 = `
      &amp;lt;svg xmlns="http://www.w3.org/2000/svg" width="60" height="60"&amp;gt;
        &amp;lt;image href="${imageUrl}" x="10" y="10" height="40" width="40" /&amp;gt;
      &amp;lt;/svg&amp;gt;
    `;

    const parser = new DOMParser();
    return parser.parseFromString(svg, "image/svg+xml").documentElement;
  }

  generateRandomMarkers() {
    for (let i = 0; i &amp;lt; 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
      });
    }
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  map.html
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;google-map height="100vh" width="100vw" [options]="options"&amp;gt;

  &amp;lt;map-marker-clusterer [renderer]="renderer"&amp;gt;

    @for (pokemon of markerPositions; track $index) {
      &amp;lt;map-advanced-marker 
        [position]="pokemon.position" 
        [options]="{ content: pokemon.iconHTML }"&amp;gt;
      &amp;lt;/map-advanced-marker&amp;gt;
    }

  &amp;lt;/map-marker-clusterer&amp;gt;

  &amp;lt;map-polygon [paths]="polygonPaths" [options]="polygonOptions"&amp;gt;
  &amp;lt;/map-polygon&amp;gt;

&amp;lt;/google-map&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  🧩 Example 2 — The OLD Google Maps JavaScript SDK (Everything in TS)
&lt;/h2&gt;

&lt;p&gt;This is the classic way: you manually create the map and markers using the JavaScript Maps SDK and manipulate the map directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;✔️ Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete control of the Google Maps API&lt;/li&gt;
&lt;li&gt;Access to advanced features not yet supported by Angular wrappers&lt;/li&gt;
&lt;li&gt;You can integrate any third-party library easily&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;❌ Disadvantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More verbose&lt;/li&gt;
&lt;li&gt;More DOM manipulation&lt;/li&gt;
&lt;li&gt;Harder to maintain&lt;/li&gt;
&lt;li&gt;Change detection is manual&lt;/li&gt;
&lt;li&gt;Harder to use with SSR&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  📌 Full Component (TypeScript Version)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;map-google.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  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 }) =&amp;gt; {
          // Create a render with count and position

        }
      }
    });
  }

  attachEvents() {
    this.map.addListener("idle", () =&amp;gt; {
      this.showVisibleMarkers();
    });

    this.map.addListener("bounds_changed", () =&amp;gt; {
      const zoom = this.map.getZoom();
      if (zoom &amp;amp;&amp;amp; zoom &amp;gt; 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 =&amp;gt; {
    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 =&amp;gt; !newVisible.includes(old)
  );

  // 4 — added markers (they were not visible before but are now)
  const added = newVisible.filter(
    curr =&amp;gt; !prev.includes(curr)
  );

  // 5 — apply changes directly to the clusterer
  if (removed.length &amp;gt; 0) {
    this.clusterer.removeMarkers(removed);
  }

  if (added.length &amp;gt; 0) {
    this.clusterer.addMarkers(added);
  }

  // 6 — update the list of visible markers
  this.visibleMarkers = newVisible;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;map-google.html&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div #mapContainer id="map" style="width: 100vw; height: 100vh;"&amp;gt;&amp;lt;/div&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔥 Results: 5,000 Animated Pokémon Markers + Clustering
&lt;/h2&gt;

&lt;p&gt;Both approaches generate:&lt;/p&gt;

&lt;p&gt;✔️ 5,000 random animated Pokémon GIF markers&lt;br&gt;
✔️ Custom Pokéball SVG cluster icons&lt;br&gt;
✔️ Dynamic bounds detection&lt;br&gt;
✔️ SuperCluster algorithm&lt;br&gt;
✔️ Marker visibility updates&lt;br&gt;
✔️ Polygon rendering (HTML version only)&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚔️ Comparison Table — Which One Should You Use?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3adqr1lchvww420i90zt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3adqr1lchvww420i90zt.png" alt="Comparison Table" width="653" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 Conclusion
&lt;/h2&gt;

&lt;p&gt;Google Maps now integrates beautifully with Angular 20, and depending on your use case you can choose between:&lt;/p&gt;

&lt;p&gt;✅ Native Angular Components (Recommended for most apps)&lt;/p&gt;

&lt;p&gt;Cleaner, reactive, simpler, SSR-friendly.&lt;br&gt;
Perfect for dashboards, admin panels, or apps that need maintainability.&lt;/p&gt;

&lt;p&gt;✔️ Raw Google Maps JS API&lt;/p&gt;

&lt;p&gt;More powerful and flexible.&lt;br&gt;
Perfect for complex GIS systems, map editors, and advanced visualizations.&lt;/p&gt;

&lt;p&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  🌟 Advanced Tips &amp;amp; Extra Possibilities
&lt;/h2&gt;

&lt;p&gt;Even with the examples above, you can do much more with Google Maps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple polygons and polylines: Add as many shapes as needed, each with its own styling and interactivity.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;Interactive objects: Any clickable object can open an InfoWindow or display details when clicked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By combining MVCArray groups and InfoWindows, you maintain high performance and clean organization even with thousands of markers or complex shapes.&lt;/p&gt;

&lt;p&gt;🔗 Further Reading / Useful Links&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/maps/documentation/javascript/infowindows" rel="noopener noreferrer"&gt;Google Maps JavaScript API – InfoWindows&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/maps/documentation/javascript/shapes" rel="noopener noreferrer"&gt;Google Maps JavaScript API – Polygons and Polylines&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/maps/documentation/javascript/reference/event#MVCArray" rel="noopener noreferrer"&gt;Google Maps JavaScript API – MVCArray (Layers)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/maps/documentation/javascript/marker-clustering" rel="noopener noreferrer"&gt;Marker Clustering with Google Maps&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftf0rr2gs0v6e5jde48nw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftf0rr2gs0v6e5jde48nw.png" alt="Google Map with Pokemon" width="800" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>googlemaps</category>
      <category>tutorial</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
