Google Maps is a powerful tool for any application that needs location-based functionality. However, integrating it into an Angular application can sometimes lead to performance challenges.
For example, in one of our projects that use Google Maps, we noticed a significant performance issue. Each time a page with a map is loaded, the application reserves an additional 200–400 MB of RAM. This is expected because Google Maps needs to load the script, cache the tiles, etc. However, what was unexpected is that when we navigate away from the page, that reserved 200–400 MB of memory was not released.
After investigating the issue, we decided to address it by creating a single, reusable instance of the map. This approach ensures that the same map instance is used whenever a page with a map is visited, preventing additional memory from being allocated each time a map is shown, improving overall performance.
In this blog I’ll guide you step-by-step through how to set up a single instance of Google Maps in an Angular application using the @angular/google-maps library.
Why Use @angular/google-maps?
The @angular/google-maps library provides Angular-specific components and directives to easily integrate Google Maps with your application. It simplifies the integration process, taking advantage of Angular’s dependency injection and reactive forms, and ensures compatibility with Angular’s ecosystem.
Install and Configure @angular/google-maps
Install the Library
Run the following command to install the package:
ng add @angular/google-maps.
Loading the API
Include the Dynamic Library Import script in the index.html of your app. When a Google Map is being rendered, it’ll use the Dynamic Import API to load the necessary JavaScript automatically.
<!-- index.html -->
<!DOCTYPE html>
<body>
...
<script>
(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
v: "weekly",
key: YOUR_API_KEY_GOES_HERE
});
</script>
</body>
</html>
Create Map Container Component
The MapContainerComponent is a standalone Angular component we will create to render the Google Map. It will use Angular’s GoogleMap component to embed the map in your application.
Run the following command to create the map container component:
ng generate component components/map-container
map-container.ts
import { Component, OnDestroy, OnInit } from '@angular/core';
import { GoogleMap } from '@angular/google-maps';
@Component({
selector: 'app-map-container',
standalone: true,
imports: [
GoogleMap
],
templateUrl: './map-container.component.html',
styleUrl: './map-container.component.scss'
})
export class MapContainerComponent implements OnInit, OnDestroy {
// Add your Google Maps functionality here.
constructor() {
console.log('constructor MapContainerComponent');
}
ngOnInit() {
console.log('ngOnInit MapContainerComponent');
}
ngOnDestroy(): void {
console.log('ngOnDestroy MapContainerComponent');
}
}
Explanation
- Imports the
GoogleMapComponent: This integrates the Angular Google Maps library into the container component. - The
constructor,ngOnInit, andngOnDestroymethods includeconsole.logstatements to verify when the component is initialized and destroyed. This is useful for debugging and validating the single-instance behavior.
map-container.html
<google-map class="map"
width="100%"
height="100%"
>
/**
* Add MapMarker, MapPolyline etc here.
* More here: https://github.com/angular/components/tree/main/src/google-maps#components
*/
</google-map>
This template displays the map within the defined dimensions and allows you to add features like markers and polylines. More Google maps components can be found in the official documentation.
Create a Reusable Map Service
The ReusableMapService is when we will create to manage the lifecycle and single-instance behavior of the map. It uses Angular’s dependency injection system to ensure only one instance of the service is created and used across the application.
Run the following command to generate the service:
ng generate service services/reusable-map
reusable-map.service.ts
import {
ComponentRef,
createComponent,
EnvironmentInjector,
inject,
Injectable,
OnDestroy,
Signal,
signal,
ViewContainerRef,
WritableSignal
} from '@angular/core';
import { MapContainerComponent } from './components/map-container/map-container.component';
import { NavigationStart, Router } from '@angular/router';
import { filter } from 'rxjs';
/**
* Service to manage the reusable map component.
*/
@Injectable({
providedIn: 'root'
})
export class ReusableMapService implements OnDestroy {
private injector: EnvironmentInjector = inject(EnvironmentInjector);
private router: Router = inject(Router);
private _mapComponentRef!: ComponentRef<MapContainerComponent>;
private _currentViewContainerRef: ViewContainerRef | null = null;
private _mapComponentRefInitialized: WritableSignal<boolean> = signal(false);
public mapComponentRefInitialized: Signal<boolean> = this._mapComponentRefInitialized.asReadonly();
constructor() {
this._routerSubscription();
}
/**
* Attaches the map component to the given view container.
* @param viewContainerRef - The view container reference to attach the map component to.
*/
public attachComponent(viewContainerRef: ViewContainerRef): void {
if (!viewContainerRef) {
return;
}
if (!this._mapComponentRef) {
this._createComponent();
}
this._currentViewContainerRef = viewContainerRef;
viewContainerRef.insert(this._mapComponentRef.hostView);
this._mapComponentRef.changeDetectorRef.detectChanges();
}
/**
* Detaches the map component from the given view container.
*/
public detachComponent(): void {
if (!this._mapComponentRef || !this._currentViewContainerRef) {
return;
}
this._currentViewContainerRef.detach(
this._currentViewContainerRef.indexOf(this._mapComponentRef.hostView)
);
this._currentViewContainerRef = null;
}
/**
* Sets an input property on the map component.
* @param key - The input property key.
* @param val - The value to set for the input property.
* @template T - The type of the value.
*/
public setComponentInput<T = unknown>(key: string, val: T): void {
if (!this._mapComponentRef || !key) {
return;
}
this._mapComponentRef.setInput(key, val);
}
/**
* Lifecycle hook that is called when the service is destroyed.
*/
ngOnDestroy(): void {
this._mapComponentRefInitialized.set(false);
this.detachComponent();
if (this._mapComponentRef) {
this._mapComponentRef.destroy();
}
}
/**
* Creates the map component if it does not already exist.
* @private
*/
private _createComponent(): void {
if (this._mapComponentRef) {
return;
}
try {
this._mapComponentRef = createComponent(MapContainerComponent, { environmentInjector: this.injector });
this._mapComponentRefInitialized.set(true);
} catch (error) {
this._mapComponentRefInitialized.set(false);
}
}
/**
* Subscribes to router events to handle detaching the map component on navigation.
* @private
*/
private _routerSubscription(): void {
this.router.events.pipe(
filter(event => event instanceof NavigationStart && !!this._currentViewContainerRef),
)
.subscribe(event => {
this.detachComponent();
});
}
}
Explanation:
- Singleton Behavior: The
providedIn: 'root'ensures that only one instance of this service exists in the entire application. - Dynamic Attachment: The service can dynamically attach and detach the map component to a view container using methods like
attachComponent()anddetachComponent(). - Lifecycle Management: The
ngOnDestroymethod cleans up resources when the service is destroyed.
Create Reusable Map Directive
Finally, we will crate the ReusableMapDirective to simplify attaching the map component to any Angular template. It uses the reusable map service to handle the attachment logic.
Run the following command to generate the directive:
ng generate directive directives/reusable-map
reusable-map.directive.ts
import { Directive, inject, OnInit, ViewContainerRef } from '@angular/core';
import { ReusableMapService } from './reusable-map.service';
/**
* Directive to attach a reusable map component to a view container.
*
* @selector [reusable-map]
* @standalone true
*/
@Directive({
selector: '[reusable-map]',
standalone: true
})
export class ReusableMapDirective implements OnInit {
/**
* Reference to the view container where the map component will be attached.
* @private
*/
private readonly _viewContainerRef: ViewContainerRef = inject(ViewContainerRef);
/**
* Service to help with map-related operations.
* @private
*/
private readonly _reusableService: ReusableMapService = inject(ReusableMapService);
/**
* Lifecycle hook that is called after the directive's data-bound properties are initialized.
* Attaches the map component to the view container.
*/
public ngOnInit(): void {
this._attachMapComponent();
}
/**
* Attaches the map component to the view container.
* @private
*/
private _attachMapComponent(): void {
this._reusableService.attachComponent(this._viewContainerRef);
}
}
Explanation:
- ViewContainerRef: This allows the directive to dynamically attach the reusable map component to the template’s view container.
- ReusableMapService Integration: The directive delegates the actual attachment logic to the service.
Use Single Instance of MapContainerComponent
I’ve hosted the reusable map directive on the MapPageComponent, which serves as the entry point for displaying the map in my Angular application. This component uses the ReusableMapDirective to attach the single instance of the map to the page, ensuring consistency and efficient resource usage.
Purpose of the Map Page Component
- Integrate the Reusable Map Directive: The component demonstrates how to include and utilize the
ReusableMapDirectivein a template. - Host the Map Instance: This component acts as the container where the reusable map instance is displayed.
- Customizable Entry Point: Developers can add custom functionality like buttons, overlays, or event listeners specific to this page.
map.component.ts
import { Component } from '@angular/core';
import { ReusableMapDirective } from '../../reusable-map.directive';
@Component({
selector: 'app-map-page',
standalone: true,
templateUrl: './map-page.component.html',
imports: [
ReusableMapDirective
],
styleUrl: './map-page.component.scss'
})
export class MapPageComponent {
}
map.component.html
<ng-template reusable-map></ng-template>
Explanation:
reusable-map: The directive handles the logic of attaching and managing the map instance, abstracting the complexity from this component.<ng-template>: This Angular structural directive allows dynamic rendering of the map component. The reusable map directive attaches theMapContainerComponentto this placeholder.
Result
Ready to see the magic? Check out the video below and notice how the console logs from the MapContainerComponent are displayed only when we visit the map page for the first time. For subsequent visits, the same instance of the MapContainerComponent is reused.
Summary
By implementing a single, reusable map instance, you can significantly improve the performance and resource management of Angular applications that rely on Google Maps. The steps outlined above offer a streamlined approach to integrating maps with Angular, making your application more efficient and user-friendly.
However, building scalable, high-performance applications often involves challenges that go beyond what a single blog post can address. That’s where Trailhead comes in. With deep expertise in custom software development and optimizing complex integrations like Google Maps in Angular, our team can help take your application to the next level.
Ready to optimize your software and tackle your toughest technical challenges? Contact Trailhead today to see how we can partner with you to deliver innovative, efficient, and scalable solutions.


