Exploring Google Places with Angular 2, Typescript, and Ionic 2

Has anyone successfully transformed this code into TypeScript?

  var map;
  var infowindow;

  function initMap() {
    var pyrmont = {lat: -33.867, lng: 151.195};

    map = new google.maps.Map(document.getElementById('map'), {
      center: pyrmont,
      zoom: 15
    });

    infowindow = new google.maps.InfoWindow();
    var service = new google.maps.places.PlacesService(map);
    service.nearbySearch({
      location: pyrmont,
      radius: 500,
      type: ['store']
    }, callback);
  }

  function callback(results, status) {
    if (status === google.maps.places.PlacesServiceStatus.OK) {
      for (var i = 0; i < results.length; i++) {
        createMarker(results[i]);
      }
    }
  }

  function createMarker(place) {
    var placeLoc = place.geometry.location;
    var marker = new google.maps.Marker({
      map: map,
      position: place.geometry.location
    });

    google.maps.event.addListener(marker, 'click', function() {
      infowindow.setContent(place.name);
      infowindow.open(map, this);
    });
  }

I attempted to convert it myself with the code below but encountered an error in the console which I will display after the code. If anyone can point out where I may be going wrong, it would be greatly appreciated.

  constructor(public navCtrl: NavController, private navParams: NavParams, private ngZone: NgZone) {}

  ionViewDidLoad() {
    console.log('ionViewDidLoad DetailsPage');
    this.loadMap();
  }

  @ViewChild('map') mapElement: ElementRef;
  map: any;

  loadMap(){

    Geolocation.getCurrentPosition().then((position) => {

      let latLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);

      let mapOptions = {
        center: latLng,
        zoom: 15,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      }

      this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);

      let service = new google.maps.places.PlacesService(mapOptions);

      service.nearbySearch({
        location: latLng,
        radius: 500,
        type: ['pub']
      }, (results, status) => {
          this.callback(results, status)
      });

    }, (err) => {
      console.log(err);
    });

  }

  callback(results, status) {
    if (status === google.maps.places.PlacesServiceStatus.OK) {
      for (var i = 0; i < results.length; i++) {
        this.createMarker(results[i]);
      }
    }
  }

  createMarker(place){
    var placeLoc = place.geometry.location;
    var marker = new google.maps.Marker({
        map: this.map,
        position: place.geometry.location
    });

    let infowindow = new google.maps.InfoWindow();

    google.maps.event.addListener(marker, 'click', () => {
      this.ngZone.run(() => {
        infowindow.setContent(place.name);
        infowindow.open(this.map,this);
      });
    });
  }

  addMarker(){

    let marker = new google.maps.Marker({
      map: this.map,
      animation: google.maps.Animation.DROP,
      position: this.map.getCenter()
    });

    let content = "<h4>You are here!</h4>";          

    this.addInfoWindow(marker, content);

  }

  addInfoWindow(marker, content){

    let infoWindow = new google.maps.InfoWindow({
      content: content
    });

    google.maps.event.addListener(marker, 'click', () => {
      this.ngZone.run(() => {
        infoWindow.open(this.map, marker);
      });
    });
  }

Error message displayed in the console:

Uncaught TypeError: this.b.getElementsByTagName is not a function
    at B5.attributionText_changed (places_impl.js:28)
    at zb (js?key=AIzaSyAZTATsl9JjVKodXRnzeSk52Ndvtz-HPPU&libraries=places:36)
    at B5._.y.bindTo (js?key=AIzaSyAZTATsl9JjVKodXRnzeSk52Ndvtz-HPPU&libraries=places:104)
    at Object.f6.f (places_impl.js:38)
    at Hw.<anonymous> (js?key=AIzaSyAZTATsl9JjVKodXRnzeSk52Ndvtz-HPPU&libraries=places:137)
    at js?key=AIzaSyAZTATsl9JjVKodXRnzeSk52Ndvtz-HPPU&libraries=places:105
    at Object.<anonymous> (js?key=AIzaSyAZTATsl9JjVKodXRnzeSk52Ndvtz-HPPU&libraries=places:37)
    at js?key=AIzaSyAZTATsl9JjVKodXRnzeSk52Ndvtz-HPPU&libraries=places:105
    at js?key=AIzaSyAZTATsl9JjVKodXRnzeSk52Ndvtz-HPPU&libraries=places:37
    at js?key=AIzaSyAZTATsl9JjVKodXRnzeSk52Ndvtz-HPPU&libraries=places:105

The specific portion highlighted in the console file is causing the issue.

Answer №1

When you add a callback function, the this context may be lost. There are various ways to resolve this issue. One approach is to wrap it in an anonymous function:

service.nearbySearch({
  location: latLng,
  radius: 500,
  type: ['pub']
}, (results, status) => {
  this.callback(results, status);
});

It's important to note that using addListener calls from google.maps does not trigger a change detection cycle. For more details, check out this answer. You might encounter this problem with your addInfoWindow function:

@Component({
    selector : 'maps-test',
    template : '<div #map></div>'
})
export class MapsTestComponent {

  @ViewChild('map')
  mapElement: ElementRef;

  constructor(private ngZone: NgZone){}

  ngAfterViewInit() {
      loadMap();
  }

  loadMap(){

    Geolocation.getCurrentPosition().then((position) => {

      let latLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);

      let mapOptions = {
        center: latLng,
        zoom: 15,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      }

      this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);

            let service = new google.maps.places.PlacesService(this.map);


      service.nearbySearch({
        location: latLng,
        radius: 500,
        type: ['pub']
      }, (results, status) => {
          this.callback(results, status)
      });

    }, (err) => {
      console.log(err);
    });

  }

  callback(results, status) {
    if (status === google.maps.places.PlacesServiceStatus.OK) {
      for (var i = 0; i < results.length; i++) {
        this.createMarker(results[i]);
      }
    }
  }

  createMarker(place){
    var placeLoc = place.geometry.location;
    var marker = new google.maps.Marker({
        map: this.map,
        position: place.geometry.location
    });

    let infowindow = new google.maps.InfoWindow();

    google.maps.event.addListener(marker, 'click', () => {
      this.ngZone.run(() => {
        infowindow.setContent(place.name);
        infowindow.open(this.map,this);
      });
    });
  }

  addMarker(){

    let marker = new google.maps.Marker({
      map: this.map,
      animation: google.maps.Animation.DROP,
      position: this.map.getCenter()
    });

    let content = "<h4>You are here!</h4>";          

    this.addInfoWindow(marker, content);

  }

  addInfoWindow(marker, content){

    let infoWindow = new google.maps.InfoWindow({
      content: content
    });

    google.maps.event.addListener(marker, 'click', () => {
      this.ngZone.run(() => {
        infoWindow.open(this.map, marker);
      });
    });
  }
}

Answer №2

Just a quick update on this solution. I implemented this code in an Ionic application, which is based on Angular. It worked for me after making a small adjustment. Initially, the infowindow was not opening and kept throwing a

Uncaught TypeError: this.b.getElementsByTagName is not a function
error. To resolve this issue, I made a change to the following:

google.maps.event.addListener(marker, 'click', () => {
  this.ngZone.run(() => {
    infowindow.setContent(place.name);
    infowindow.open(this.map,this);
  });

to

google.maps.event.addListener(marker, 'click', () => {
      this.ngZone.run(() => {
        infowindow.setContent(place.name);
        infowindow.open(this.map,this.infowindow);
      });

by adding

this.infowindow

This small adjustment solved the issue. Hopefully, this helps anyone facing the same problem as me.

Answer №3

Thank you for your inquiry. I recently converted all relevant data types to TypeScript for a project I was working on.

In addition, I customized the PlaceResult by appending a custom URL in the backend of my application. To tailor it to my specific needs, I used an interface to extend the type functionality.

// For detailed documentation, please refer to: https://developers.google.com/maps/documentation/places/web-service/search-nearby#nearby-search-responses

export interface RRPlaceResult extends PlaceResult {
    url: string;
}

// Define the structure of the PlaceResult object
export type PlaceResult = {
    html_attributions: string[];
    results: Place[];
    status: PlacesSearchStatus;
    error_message?: string;
    info_messages?: string[];
    next_page_token?: string;
};

// Define the properties of a Place object
export type Place = {
    // List of properties omitted for brevity
};

// Define possible values for the PlacesSearchStatus
export type PlacesSearchStatus = 'OK' | 'ZERO_REULTS' |
    'INVALID_REQUEST' | 'OVER_QUERY_LIMIT' | 'REQUEST_DENIED' |
    'UNKNOWN_ERROR';

// Additional type definitions for AddressComponent, PlaceEditorialSummary, Geometry, etc.
// Types omitted for brevity

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

Unable to store the outcomes from [ngbTypeahead] in [resultTemplate]

I'm trying to integrate ngbTypeahead into my HTML using the code snippet below <ng-template #rt let-r="result" let-t="term"> <ngb-highlight [result]="r.FirstName" [term]="t"></ngb-highlight> </ng-template> <input name ...

The object literal can only define properties that are already known, and 'data' is not found in the type 'PromiseLike<T>'

When making a request to a server with my method, the data returned can vary in shape based on the URL. Previously, I would cast the expected interface into the returned object like this: const data = Promise.resolve(makeSignedRequest(requestParamete ...

What are some secure methods for integrating iframes into an Angular 7 project?

Is there a secure method to set the URL for an iframe without bypassing security measures like using this.domsanitizer.bypassSecurityTrustResourceUrl(url)? While this approach may be flagged as a high vulnerability by tools such as Veracode, is there a w ...

Creating a fresh JSON structure by utilizing an established one

I have a JSON data that contains sections and rubrics, but I only need the items for a new listing. The new object named 'items' should consist of an array of all the items. The final JSON output should be sorted by the attribute 'name&apos ...

Angular's ng toolkit universal experiencing loading issues and tools are malfunctioning

I encountered the following issue npm run build: prod commands are not functioning correctly ERROR: Cannot read property 'length' of undefined ERROR: If you think that this error shouldn't occur, please report it here: https://githu ...

Tips on handling communication between different feature modules each handling their own portion of the state

I am currently working on an Angular application that consists of multiple feature modules. My goal is to implement ngrx store in such a way that each module manages its own state. // app.module.ts ... imports: [ ... StoreModule.forRoot(reducers) ...

Angular 11 project facing issues with Bootstrap 5 tooltip functionality

Embracing the power of Bootstrap 5.0.2 in an Angular 11 project, I included the following code: index.html <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compa ...

Creating a Connection between Angular 4 and MySQL Database using PHP for Data Posting

After spending a few weeks searching for the answer to my question, I still haven't found what I'm looking for. I understand that Angular is a front-end framework and that I have freedom to choose any database and backend for my project. Current ...

Using SCSS based on the browser language in Angular: A Step-by-Step Guide

Is there a way to implement SCSS that is dependent on the user's browser language? When I checked, I found the browser language specified in <html lang = "de"> and in the CSS code as html[Attributes Style] {-webkit-locale: "en&quo ...

The Vercel/NextJS deployment does not delay the completion of the serverless function until the email is sent via Azure

Upon a user's registration, I am attempting to send a registration/account activation email. While the email sends successfully via Azure's email services when running on localhost, deployments on Vercel do not trigger the email (although the use ...

Setting up a new package through JPSM installation

I utilize jspm in my web application. My goal is to install the npm:angular2 package without needing to set it up in the config.js file. Instead of loading the angular2 module via jspm, I manually added the angular2 library like this: <script src="jspm ...

Exploring the SOLID Design Principles through TypeScript

Recently, I came across an intriguing article discussing the SOLID principles and delving into the concept of the Dependency Inversion Principle (DIP). The article presented an example to illustrate this principle using both an incorrect and a correct appr ...

Using ES6 import with the 'request' npm module: A Step-by-Step Guide

When updating TypeScript code to ES6 (which runs in the browser and Node server, with a goal of tree-shaking the browser bundle), I am attempting to replace all instances of require with import. However, I encountered an issue... import * as request from ...

Angular seems to be experiencing issues with maintaining context when executing a function reference for a base class method

Imagine we have CtrlOne that extends CtrlTwo, with a componentOne instantiated in the template of CtrlOne. Here is some code to illustrate the issue: class CtrlOne extends CtrlTwo { constructor() { super(); } } class CtrlTwo { sayMyName(name: st ...

What causes error TS2339 to occur: The property 'classList' is not found on type 'never'

Currently, I am working on creating a basic component called "BackToTop" const BackToTop: React.FC = () => { const bttEl = useRef(null); function scrollHandler(): void { var bttHtmlEl: HTMLElement | null = bttEl.current; if (bttH ...

Avoiding data type conversion in JavaScript/TypeScript

Currently delving into the world of JavaScript, I come from a background of working with statically typed languages. Naturally, I opted to dive into TypeScript instead of starting directly with JS. While TypeScript is great and addresses many issues presen ...

Struggling to retrieve the 'this' object using a dynamic string

Within my Nuxt + TS App, I have a method that attempts to call a function: nextPage(paginationName: string): void { this[`${paginationName}Data`].pagination .nextPage() .then((newPage: number) => { this.getData(pagination ...

I'm currently facing an issue where the Ionic styles are not displaying correctly within my Next.js application

I'm currently experiencing a problem where Ionic styles are not being applied to certain pages in my Next.js app. Despite working on the home page, the Ionic styles do not seem to be taking effect on the profile page. Any guidance on troubleshooting t ...

Tips for sorting multiple rows based on the primary column in MUI DataGrid ReactJS

https://i.stack.imgur.com/T9ODr.png Is there a way to utilize Material UI DataGrid to build a table that matches the structure displayed in the linked image? I have successfully created a basic table with DataGrid, but I'm struggling to add multiple ...

Transform an Excel spreadsheet into Json format effortlessly with Kendo's powerful tools

My upload feature allows users to upload an Excel sheet, and the backend API expects the data in JSON format. Here is an example of the expected input: [{ "LineNumber": "1", "Parma": 123, "PartNumber": 234, "Tes ...