Introduction: Utilizing Google Maps API with Angular-CLI - A Beginner's Guide

After just starting with ng2, I decided to create a project using angular-cli. However, I soon realized that I needed some third-party modules such as Google Maps API, Lodash, and JQuery. Although I have a basic understanding of Typescript, I was unsure how to incorporate these modules into an Angular2 app without relying on existing ng2 modules or components built on top of them.

In the past, when working with JavaScript, I would simply include the JS file and reference the API documentation to know which methods to use. With Angular2, I wasn't sure what steps to take to achieve the same result.

My research led me to believe that I should first install the necessary Typescript files. For example, for Google Maps, I used

npm install --save @types/google-maps
. Now, my question is whether I need to import this module into my Angular app by including it in app.module.ts, in the imports array, or if it is globally available.

One source suggested installing it with npm and then adding a reference to the library in the scripts array of my angular-cli.json like this:

"scripts": ['./node_modules/googlemaps/googemaps.min.js'],

Which method is best for installing the Google Maps API? Considering that the rest of my Angular app will be written in Typescript, I assumed Typescript might be the way to go.

Now, in my app.component.ts, I want to create a simple map using the Typescript version of Google Maps that I installed. How can I accomplish this? The Google Maps API recommends creating a new instance of the map like this:

var map = new google.maps.Map(document.getElementById('map'), {
          center: {lat: -34.397, lng: 150.644},
          scrollwheel: false,
          zoom: 8
        });

But how do I do this using the Typescript version of Google Maps that I just installed?

Would the Typescript version of Google Maps have all the same methods as the original API? Are there documentation sites for typescripts of popular JS libraries that I can refer to?

Answer №1

The project agm recommended by @Steve G. serves as a good starting point, but in some cases (such as managing your own request policy), you might prefer creating your own typed wrapper. Here's how I achieved this using Angular Cli:

Step One:

npm i --save @types/googlemaps

Next, add the types to your tsconfig.app.json:

"types": ["googlemaps"]

Finally, write your TypeScript code:

// No imports here

// Replace this with any value without an ID_KEY
const getScriptSrc = (callbackName) => {
  return `https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=${callbackName}`;
}

export class GMapService {

  private map: google.maps.Map;
  private geocoder: google.maps.Geocoder;
  private scriptLoadingPromise: Promise<void>;

  constructor() {
        // Loading script
        this.loadScriptLoadingPromise();
        // Load other components
        this.onReady().then(() => {
          this.geocoder = new google.maps.Geocoder();
        });
  }

  onReady(): Promise<void> {
    return this.scriptLoadingPromise;
  }

  initMap(mapHtmlElement: HTMLElement, options: google.maps.MapOptions): Promise<google.maps.Map> {
    return this.onReady().then(() => {
      return this.map = new google.maps.Map(mapHtmlElement, options);
    });
  }

  private loadScriptLoadingPromise() {
    const script = window.document.createElement('script');
    script.type = 'text/javascript';
    script.async = true;
    script.defer = true;
    const callbackName: string = 'UNIQUE_NAME_HERE';
    script.src = getScriptSrc(callbackName);
    this.scriptLoadingPromise = new Promise<void>((resolve: Function, reject: Function) => {
      (window as any)[callbackName] = () => { resolve(); };

      script.onerror = (error: Event) => { reject(error); };
    });
    window.document.body.appendChild(script);
  }

  /** Example of wrapping API calls in promises */
  geocode(address: string | google.maps.GeocoderRequest): Promise<google.maps.GeocoderResult[]> {
    return this.onReady().then(() => {
      this.geocoder.geocode(typeof address === "google.maps.GeocoderRequest" ? address : {address: address},
          (results: google.maps.GeocoderResult[], status: google.maps.GeocoderStatus) => {
            if(status === google.maps.GeocoderStatus.OK) {
              return results;
            } else {
              throw new Error(status.toString());
            }
      });
    });
  });

}

So your map component will appear as follows:

@Component({
  selector: 'app-gmap-wrapper',
  template: '<div #map style="width:400px; height:300px">loading...</div>'
})
export class GMapWrapperComponent implements OnInit {
    @ViewChild('map') mapRef: ElementRef;

    constructor(private gmapService: GMapService) { }

    ngOnInit() {
      this.gmapService.initMap(this.mapRef.nativeElement, {
        center: {lat: 1234.1234, lng: 1234.1234},
        scrollwheel: true,
        zoom: 8
      });
    }
}

This code can be improved by removing the namespace google.maps from all types. Any suggestions?

Answer №2

When I encountered issues with AGM in components and nu-maps, I decided to switch to using plain Google API JavaScript which I found to be much better:

If you're having similar troubles, I recommend giving it a try: https://cloud.google.com/maps-platform/?hl=de

  • Simpler to use
  • Well-documented
  • Not reliant on individuals who don't merge pull requests
  • It just works!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

TypeScript has encountered an issue where a specific type A cannot be assigned to another type A, even though

Encountering a Typescript issue where it claims that type A is not compatible with type A, even though they are identical: Check out this playground link for the code snippet in context: declare interface ButtonInteraction { user: any; customId: strin ...

Adjusting the dimensions of the cropper for optimal image cropping

I am currently working on integrating an image cropper component into my project, using the react-cropper package. However, I am facing a challenge in defining a fixed width and height for the cropper box such as "width:200px; height:300px;" impo ...

Ways to customize the placeholder for Material input fields in Angular 5

Is it possible to customize the placeholder color in an Angular Material input field? <mat-form-field> <input matInput placeholder="Name"> </mat-form-field> ...

Creating a new model in TypeScript may encounter a declaration issue with getting

I may just be overlooking something, but I have the following model: import { Brand } from './brand'; import { Plan } from './plan'; import { Venue } from './venue'; export class Subscription { id: number; brandId: number ...

Maximizing the power of Webpack alongside Google Maps API

I have been using Webpack along with the html-webpack-plugin to compile all my static files. However, I am facing an issue when integrating it with the Google Maps API. Here is the code snippet: var map; function initMap() { map = new google.maps.Map(d ...

Get the latest Angular updates by running the command: "ng update @angular/cli @angular/core --allow-dirty --

Upon updating my Angular project from v8 to the latest version, I encountered a problem. The original content of my package.json was like this: { "name": "website-admin-angular", "version": "0.0.0", " ...

Encountering an issue with Jest when using jest.spyOn() and mockReturnValueOnce causing an error

jest --passWithNoTests --silent --noStackTrace --runInBand --watch -c jest-unit-config.js Project repository An error occurred at jest.spyOn(bcrypt, 'hash').mockRejectedValue(new Error('Async error message')) Error TS2345: The argum ...

Lack of Intellisense in Sveltekit for the generated $types module on WebStorm

Is there a setting in WebStorm to enable intellisense for automatically generated ./$types by SvelteKit? I'm writing without any minimal example, just curious. Everything is done according to SvelteKit's documentation. Using satisfies LayoutLoad ...

Issue with Angular DevExtreme error popup

Currently, I am working on a project using Angular and DevExtreme. Whenever an error occurs, the error message seems to be hidden behind the popup form. Can anyone advise me on how to bring it to the front? Any help would be greatly appreciated. https://i ...

Issue with Bootstrap 4 column width when using Angular 2 *ngFor

I have a container that displays search results. The layout is set up using Bootstrap columns, but there seems to be excessive padding or margin causing the results to appear very narrow. Interestingly, if I manually input each result instead of using *ngF ...

I am currently working on establishing a connection between Angular and MongoDB

I built a basic Angular application currently running on Local Host 4200. What is the process for saving form values and storing them in a MongoDB database? ...

Finding items in the database using their identification numbers

I have a scenario where I am accepting a list of IDs in my request, for example [1,2,3]. How can I use typeorm and querybuilder to retrieve only objects with these IDs from my database? I attempted the following: if(dto.customersIds){ Array.prototype. ...

Combining two objects/interfaces in a deep merging process, where they do not intersect, can result in a final output that does not

When attempting to merge two objects/interfaces that inherit from the same Base interface, and then use the result in a generic parameter constrained by Base, I encounter some challenges. // please be patient type ComplexDeepMerge<T, U> = { [K in ( ...

simulate express-jwt middleware functions for secure routes

I am currently facing an issue with my code snippet, which looks like this: import app from '../src/app'; beforeAll(() => jest.mock('../src/middleware/auth', () => (req: Request, res: Response, next: NextFunction) => { ...

Cypress - AG-Grid table: Typing command causing focus loss in Cell

Encountering a peculiar issue: I am attempting to input a value into the cell using 'type()'. My suspicion is that each letter typed causes the focus on the cell to be lost. It seems that due to this constant loss of focus and the 'type()& ...

What is the best way to define an event binding statement in the Angular code rather than within the template?

Is it possible to define the event binding statement directly in the code (rather than in the template)? I am trying to dynamically create a menu, and while I can achieve this with routes (since they are strings), using event names seems to be more challe ...

Incorporating New Relic into an Angular Universal application running on Node.js

I am in the process of integrating newrelic into my node.js application, which is built with angular universal and bundled using webpack. The first line in main.server.aot.ts reads: const newrelic = require('newrelic'); In addition, I have inc ...

What is the best way to change between different Angular 2 material tabs using typescript?

I need help with switching tabs using buttons <md-tab-group> <md-tab label="Tab 1">Content 1</md-tab> <md-tab label="Tab 2">Content 2</md-tab> </md-tab-group> <button md-button (click)="showTab1()">Show Tab 1< ...

What is the best way to pass the username and token data from a child component to its parent component

Hey, I'm currently working on a login app in React where the login component is a child of the app. My goal is to send back the username and token to the parent component once a user is logged in, so that I can then pass this information to other chil ...

Injecting constructor for Angular standalone component and service

I am encountering an issue with injecting a service through the constructor of a standalone component. The service is declared as Injectable with the provider set to root: @Injectable({ providedIn: 'root' }) export class ProductsService {...} ...