Hands on

Displaying Places on a HERE Map in an Angular Web Application

By Nic Raboy | 13 September 2018

If you’ve been following along, I had previously written a tutorial that demonstrated including a visual map in your Angular web application using the HERE Maps API for JavaScript. In that tutorial we saw several methods towards using a map, leading up to creating a custom component for the job. However, in that tutorial we just made use of a basic map with no markers or interaction.

Our Angular application could be substantially more useful if we had some interaction with the map. For example, what if we wanted to display all locations of a particular vendor on the map? Or what if we wanted to see address information for a particular vendor?

In this tutorial we’re going to see how to use the HERE Places API in an Angular application to search for particular points of interest and display them on a map using markers and captions, also known as information bubbles.

The Project Requirements

There are a few development requirements that must be met in order to make this tutorial a success on your own server or computer.

  1. A HERE developer account must have been created.
  2. The Angular CLI must be installed and configured.

To use the HERE maps and location services (HLS) platform, you must have an app id and app code created through your developer portal. It is free to sign up and use the HLS platform. The easiest way to develop with Angular is through the Angular CLI. It can be installed through the Node Package Manager (NPM).

While not a requirement, much of this tutorial will be a continuation to the previous tutorial I wrote, so it might be a good idea to familiarize yourself with what I had demonstrated previously.

Through this tutorial, we plan to create something as seen in the following animated image:

angular-maps-places

As you can see, there will be a map and a search box. After entering a place of interest, all matches will be displayed on the map in the form of a marker. When clicking or tapping on a marker, an information bubble will appear with the name of the place as well as the address. This is a realistic scenario on what someone might expect from a map.

Creating an Angular Project with the HERE Maps API for JavaScript

As previously mentioned, this is more or less a followup to the previous tutorial I wrote which was a getting started with HERE and Angular tutorial. To get us up to speed, I’m going to share the code from the previous tutorial, but if you want thorough explanations as to why things are the way they are, I encourage you to revisit the previous tutorial titled, Display HERE Maps within your Angular Web Application.

From the command line, execute the following to create a new Angular project with the necessary components:

ng new here-project
cd here-project
ng g component here-map

With the project and files in place, we need to copy our previous map component code into our new project. Open the project’s src/app/here-map/here-map.component.html file and include the following HTML markup:

<div #map [style.width]="width" [style.height]="height"></div>

With the UI component in place, we can create some TypeScript logic that will bind the HTML component above. Open the project’s src/app/here-map/here-map.component.ts file and include the following:

import { Component, OnInit, ViewChild, ElementRef, Input } from '@angular/core';

declare var H: any;

@Component({
    selector: 'here-map',
    templateUrl: './here-map.component.html',
    styleUrls: ['./here-map.component.css']
})
export class HereMapComponent implements OnInit {

    @ViewChild("map")
    public mapElement: ElementRef;

    @Input()
    public appId: any;

    @Input()
    public appCode: any;

    @Input()
    public lat: any;

    @Input()
    public lng: any;

    @Input()
    public width: any;

    @Input()
    public height: any;

    private platform: any;
    private map: any;

    public constructor() { }

    public ngOnInit() {
        this.platform = new H.service.Platform({
            "app_id": this.appId,
            "app_code": this.appCode
        });
    }

    public ngAfterViewInit() {
        let defaultLayers = this.platform.createDefaultLayers();
        this.map = new H.Map(
            this.mapElement.nativeElement,
            defaultLayers.normal.map,
            {
                zoom: 10,
                center: { lat: this.lat, lng: this.lng }
            }
        );
    }

}

The above TypeScript logic should be more or less what we’ve already seen. We have several inputs that can be accessed from the component, we are initializing the HLS platform, and we are creating a new map after the view has initialized.

With a map component created and readily available, it can be used in any other component. As seen previously, open the project’s src/app/app.component.html file and include the following:

<here-map
    #map
    appId="APP-ID-HERE"
    appCode="APP-CODE-HERE"
    lat="37.7397"
    lng="-121.4252"
    width="75%"
    height="530px">
</here-map>

Don’t forget to replace the appId and appCode with that of your actual project values, otherwise the map and services will fail to work. The last part to bring us up to speed is the inclusion of the HERE JavaScript libraries.

Open the project’s src/index.html file and include the following HTML markup:

<!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>

It may seem like we’ve accomplished a lot as of now, but we really just got our map up and running. Everything as of now is not the goal of this particular tutorial. It was included to prevent confusion between configuring a map and using the Places API. If you completed the previous tutorial, everything here should look familiar and can probably be skipped.

Now we can focus on the HERE Places API with marker and information bubble support.

Implementing Search Functionality with the HERE Places API

Because we are planning to use the JavaScript libraries offered by HERE, we need to include a few more in our project. As of right now we have the core library and the map service library, but we need a library for places, events on the map, as well as an interactive user interface.

Open the project’s src/index.html file and include 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">
        <link rel="stylesheet" type="text/css" href="https://js.api.here.com/v3/3.0/mapsjs-ui.css?dp-version=1533195059" />
    </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>
        <script src="https://js.api.here.com/v3/3.0/mapsjs-places.js" type="text/javascript" charset="utf-8"></script>
        <script src="https://js.api.here.com/v3/3.0/mapsjs-mapevents.js" type="text/javascript" charset="utf-8"></script>
        <script src="https://js.api.here.com/v3/3.0/mapsjs-ui.js" type="text/javascript" charset="utf-8"></script>
    </body>
</html>

Notice that this time around we’ve included three more libraries as well as a stylesheet file. This will allow us to do searches, click on markers, and add other behavior to the map.

To make use of these new libraries, a few things must be added to our map component. Open the project’s src/app/here-map/here-map.component.ts file and include the following variables:

private ui: any;
private search: any;

The goal is to initialize the search service and alter the UI behavior to be used throughout the component, hence the global variables. Within the same file, we need to alter the ngOnInit and ngAfterViewInit lifecycle events.

The ngOnInit method should now look like this:

public ngOnInit() {
    this.platform = new H.service.Platform({
        "app_id": this.appId,
        "app_code": this.appCode
    });
    this.search = new H.places.Search(this.platform.getPlacesService());
}

Since the search functionality doesn’t rely on the UI, it can be initialized prior to the view being ready, hence the ngOnInit method. In the ngAfterViewInit method, we need to prepare for some UI and event behavior:

public ngAfterViewInit() {
    let defaultLayers = this.platform.createDefaultLayers();
    this.map = new H.Map(
        this.mapElement.nativeElement,
        defaultLayers.normal.map,
        {
            zoom: 10,
            center: { lat: this.lat, lng: this.lng }
        }
    );
    let behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(this.map));
    this.ui = H.ui.UI.createDefault(this.map, defaultLayers);
}

Even though we’ve just seen a few new things to get marker support, click support, and search support on our map, it was more or less bootstrap code for the bigger picture. Now we want to add some actual functionality.

Within the project’s src/app/here-map/here-map.component.ts file, include the following two methods:

public places(query: string) {
    this.map.removeObjects(this.map.getObjects());
    this.search.request({ "q": query, "at": this.lat + "," + this.lng }, {}, data => {
        for(let i = 0; i < data.results.items.length; i++) {
            this.dropMarker({ "lat": data.results.items[i].position[0], "lng": data.results.items[i].position[1] }, data.results.items[i]);
        }
    }, error => {
        console.error(error);
    });
}

private dropMarker(coordinates: any, data: any) {
    let marker = new H.map.Marker(coordinates);
    marker.setData("<p>" + data.title + "<br>" + data.vicinity + "</p>");
    marker.addEventListener('tap', event => {
        let bubble =  new H.ui.InfoBubble(event.target.getPosition(), {
            content: event.target.getData()
        });
        this.ui.addBubble(bubble);
    }, false);
    this.map.addObject(marker);
}

So what is happening in the places method and the dropMarker methods? Let’s first examine the places method.

In the places method, we are removing any objects that might already exist on the map. This includes markers, events, etc. This is because every time the places method is called, we are trying to add new markers. It probably wouldn’t be a good idea in this scenario to append new markers to the already existing list. It makes more sense to start fresh. Once the map is cleared, we issue a search based on a provided query string. The query string represents some point of interest. We are also providing a latitude and longitude for the general search area rather than an entire global search. For any results that are found, we loop through them and call the dropMarker method.

In the dropMarker method, we are expecting some coordinate information as well as some data to be used to create an InfoBubble. Using the coordinate information we can create a marker. The marker is going to store the data that was passed which should include the place title as well as the address or vicinity. The data contains a lot more information, but for this example we’re not interested in it. We need a way to display the data, so we can create a click event. When a marker is clicked, a new information bubble is created where the content is the stored data. This bubble is added to the UI and the marker is finally added to the map.

So how do we use these places and dropMarker methods?

For this example, it makes sense to have a search form within our application to populate the location results. However, in Angular forms don’t really work by default.

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';
import { HereMapComponent } from './here-map/here-map.component';

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

In the above code we’ve included the FormsModule and added it to the imports section of the @NgModule block. Now we can use forms within our Angular application.

As seen in the image at the beginning of this tutorial, we had a search form with a pre-populated string of text. To do this, open the project’s src/app/app.component.ts file and include the following:

import { Component, OnInit } from '@angular/core';

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

    public query: string;

    public constructor() {
        this.query = "starbucks";
    }

    public ngOnInit() { }

}

The query variable is going to be bound to the form and it is also going to be used when searching for places and populating the map. It had to be declared in the TypeScript file to prevent any undefined errors.

Finally, the project can be brought to a close with the core UI of the application. Open the project’s src/app/app.component.html file and include the following:

<div style="padding: 10px 0">
    <input type="text" placeholder="Search places..." [(ngModel)]="query" />
    <button (click)="map.places(query)">Search</button>
</div>
<here-map #map appId="APP-ID-HERE" appCode="APP-CODE-HERE" lat="37.7397" lng="-121.4252" width="75%" height="530px"></here-map>

In the above HTML markup, there is a search field that is bound to the query variable in the TypeScript. There is a button that has a click event as well and it does some interesting things.

Notice the #map template variable on the actual map component. This is how we get local reference to that particular UI element. The map component has a public method called places which accepts a string for a query. Now let’s jump back into the click event for the button. We are using the local component reference, calling the places method, and passing in the query variable from the input field.

When the click event triggers, the search will occur and the markers will be placed on the map. Angular does some awesome things when it comes to binding of the data.

To run this application, execute the following from the CLI:

ng serve

You’ll probably notice that when click a marker, the information bubble isn’t the most attractive due to word wrapping. We can clean it up through some simple CSS.

Open the project’s src/styles.css file so we can add some global CSS:

.H_ib_body {
    width: 275px;
}

If you’re a much better CSS wizard than I am, you can do some pretty amazing things when it comes to the cosmetics of this application. However, from a functional perspective, you should have a map that you can search for places as of now.

Conclusion

You just saw how to use the HERE Places API to populate markers on a map for points of interest using Angular and the various JavaScript libraries offered by HERE.

This tutorial was a continuation of a previous getting started tutorial I wrote titled, Display HERE Maps within your Angular Web Application. If you haven’t seen the previous tutorial, I recommend you have a look to clear up any loose ends that this particular example with points of interest, may have brought up.