Hands on

Using the HERE Geocoder API for JavaScript in an Angular Application

By Nic Raboy | 19 September 2018

Have you ever needed to plot an address on a map that requires a latitude and longitude, but you only have a string of text representation? What about the opposite where you have a latitude and longitude, but that information isn’t valuable to you without knowing a nearest address about the information?

Being able to geocode address information into coordinates and reverse geocode coordinates into address information is a powerful necessity for many applications.

We’re going to see how to use the HERE Geocoder API for JavaScript within an Angular application so that we can take coordinates and address information and convert between the two, while getting far more information than when we started with about the query.

The Requirements

If you’ve been reading my other HERE with Angular tutorials, you’ll likely be familiar with the small set of requirements that they have. The requirements are as follows:

  1. You must have an active HERE developer account.
  2. You must have the Angular CLI installed and configured.

The good news is that the Angular CLI can be easily installed with the Node Package Manager (NPM) and the HERE developer account can be created for free on the developer portal.

Creating a New Project with the Angular CLI

To start this tutorial, we’re going to create a fresh project for simplicity. Using the Angular CLI, execute the following command:

ng new here-project

The above command will create a new project with all the Angular dependencies. Because we’ll be using the HERE JavaScript libraries, we need to include them in our project. The best way to do this is by opening the project’s src/index.html file and including the following:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>HERE with Angular</title>
        <base href="/">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="icon" type="image/x-icon" href="favicon.ico">
    </head>
    <body>
        <app-root></app-root>
        <script src="https://js.api.here.com/v3/3.0/mapsjs-core.js" type="text/javascript" charset="utf-8"></script>
        <script src="https://js.api.here.com/v3/3.0/mapsjs-service.js" type="text/javascript" charset="utf-8"></script>
    </body>
</html>

Notice that we’ve included two JavaScript libraries from the official documentation.

At this point in time we can begin actual development of our Angular application.

Developing a HERE Service to be Injected throughout the Application

When working with any group of related tasks in Angular, it is often a good idea to create a service that can be injected in any component that might appear in the application. While not absolutely necessary, it is good practice.

From the Angular CLI, execute the following to create a service:

ng g service here

For reference, any logic that communicates with the HERE Location Services (HLS) platform, will be added to our Angular service. Any logic that communicates with our UI will find its way into the corresponding Angular component.

Open the project’s src/app/here.service.ts file and include the following TypeScript code:

import { Injectable } from '@angular/core';

declare var H: any;

@Injectable({
    providedIn: 'root'
})
export class HereService {

    public platform: any;
    public geocoder: any;

    public constructor() {
        this.platform = new H.service.Platform({
            "app_id": "APP-ID-HERE",
            "app_code": "APP-CODE-HERE"
        });
        this.geocoder = this.platform.getGeocodingService();
    }

    public getAddress(query: string) {}

    public getAddressFromLatLng(query: string) {}

}

Notice that I’ve left pieces out of the service for now. Let’s take a look at what is available and work our way into it.

Because we’re working with TypeScript and the HERE API is JavaScript, we need to tell the TypeScript compiler to ignore the fact that our library doesn’t have the appropriate type definitions. This is done through the following line:

declare var H: any;

Within the Angular service, HLS must be initialized with the users app id and app code values. Both of these values can be found in the developer portal after you’ve signed in. Within the constructor method, the geocoder service must also be initialized.

With HLS and the geocoder initialized, we can have a look at each of our functions.

public getAddress(query: string) {
    return new Promise((resolve, reject) => {
        this.geocoder.geocode({ searchText: query }, result => {
            if(result.Response.View.length > 0) {
                if(result.Response.View[0].Result.length > 0) {
                    resolve(result.Response.View[0].Result);
                } else {
                    reject({ message: "no results found" });
                }
            } else {
                reject({ message: "no results found" });
            }
        }, error => {
            reject(error);
        });
    });
}

The above getAddress function is an example. When called, a new promise is created because this is an asynchronous operation. The query is passed to the geocode function and assuming results were found, they are resolved to the client. If there was an error or no results were found based on the query, then the promise will reject.

Similarly, we have the getAddressFromLatLng function:

public getAddressFromLatLng(query: string) {
    return new Promise((resolve, reject) => {
        this.geocoder.reverseGeocode({ prox: query, mode: "retrieveAddress" }, result => {
            if(result.Response.View.length > 0) {
                if(result.Response.View[0].Result.length > 0) {
                    resolve(result.Response.View[0].Result);
                } else {
                    reject({ message: "no results found" });
                }
            } else {
                reject({ message: "no results found" });
            }
        }, error => {
            reject(error);
        });
    });
}

Instead of taking a query string that might be an address, or a city, or something similar, we are providing a query string that represents a latitude and longitude. Beyond the geocode vs reverseGeocode function being used, the responses are more or less the same.

While you can do more with the HERE Geocoder API, we’re going to focus on the geocode and reverseGeocode functions for this example. With the Angular service created, we can focus on the core logic and HTML markup behind our web application.

Open the project’s src/app/app.component.ts file and include the following TypeScript code:

import { Component } from '@angular/core';
import { HereService } from "./here.service";

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {

    public query: string;
    public position: string;
    public locations: Array<any>;

    public constructor(private here: HereService) {
        this.query = "Tracy, CA";
        this.position = "37.7397,-121.4252";
    }

    public getAddress() { }

    public getAddressFromLatLng() { }

}

Again, I’ve purposefully not provided all the function code for now. Notice that we’ve imported the service that we’ve just created, defined a few public variables to be bound to the UI, and injected our service for use in the constructor method.

With the preparation work in place, we can take a look at our two functions.

public getAddress() {
    if(this.query != "") {
        this.here.getAddress(this.query).then(result => {
            this.locations = <Array<any>>result;
        }, error => {
            console.error(error);
        });
    }
}

The getAddress function acts as a wrapper to our service function. Assuming the promise resolves, we can take the result and add it to our public variable to eventually be rendered on the screen. Similarly, the getAddressFromLatLng function does the same:

public getAddressFromLatLng() {
    if(this.position != "") {
        this.here.getAddressFromLatLng(this.position).then(result => {
            this.locations = <Array<any>>result;
        }, error => {
            console.error(error);
        });
    }
}

The exception with the above function is that we’re calling a different function from the service and using a different value for our query.

The plan when it comes to the geocoder is to take user input from a form. However, in Angular by default, data binding with forms is disabled. To activate forms, open the project’s src/app/app.module.ts file and include the following:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from "@angular/forms";

import { AppComponent } from './app.component';

@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        BrowserModule,
        FormsModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }

In the above code we’ve imported FormsModule and added it to the imports array of the @NgModule block. With forms functional, we can finalize this tutorial with HTML markup. Open the project’s src/app/app.component.html file and include the following:

<div>
    <input type="text" [(ngModel)]="query">
    <button type="button" (click)="getAddress()">Search</button>
</div>
<div>
    <input type="text" [(ngModel)]="position">
    <button type="button" (click)="getAddressFromLatLng()">Search</button>
</div>
<hr>
<table style="width: 100%">
    <thead>
        <tr style="text-align: left">
            <th>Label</th>
            <th>Latitude</th>
            <th>Longitude</th>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor="let location of locations">
            <td>{% raw %}{{ location.Location.Address.Label }}{% endraw %}</td>
            <td>{% raw %}{{ location.Location.DisplayPosition.Latitude }}{% endraw %}</td>
            <td>{% raw %}{{ location.Location.DisplayPosition.Longitude }}{% endraw %}</td>
        </tr>
    </tbody>
</table>

The above HTML markup has two input fields, with two buttons, as well as a table for the results. The first input is bound to the public query variable and the button is bound to the getAddress function. Likewise, the second input is bound to the public position variable and the button is bound to the getAddressFromLatLng function.

When the locations variable is populated with results, the results are looped through and rendered in the table. While the usage scenario may be unrealistic for a production application, it is functional and will get you familiar with the HERE APIs for JavaScript with Angular.

Conclusion

You just saw how to use the HERE Geocoder API for JavaScript within an Angular application. While we created a service in this example, it wasn’t absolutely necessary. However, it is best practice given the scenario.

If you’d like to see a more realistic example of using the HERE Geocoder API, check out a previous tutorial that I wrote titled, Simple Data Processing with JavaScript and the HERE API, that uses REST instead of the JavaScript libraries.