Using Angular
This article describes how to use the HERE Maps API for JavaScript with Angular. The target is to demonstrate how to build an Angular component that displays the map and responds to the actions of the user, be it direct interaction with the map or the other components.
Setup
For the fast setup of the new Angular application we will use the Angular CLI. It provides a fast way to get started building a new single-page application. To start with Angular CLI first install it globally:
npm install -g @angular/cli
After that run the ng
command to initialize the project's scaffoldings:
ng new jsapi-angular && cd jsapi-angular
The call above will launch the interactive shell, for the simplicity of the example select to install Angular routing and use CSS as a stylesheet format:
[? Would you like to add Angular routing? No
[? Which stylesheet format would you like to use? CSS
The directory structure in the jsapi-angular
directory looks as follows. The Angular components reside in the src
directory:
jsapi-angular
├── node_modules
├── package.json
├── .gitignore
├── .browserslistrc
├── .editorconfig
├── angular.json
├── karma.conf.js
├── README.md
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.spec.json
├── dist
│ ├── ...
└── src
├── app
│ ├── app-routing.module.ts
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ └── app.module.ts
├── assets
│ ├── ...
├── environments
| ├── ...
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
└── test.ts
The recommended way to use HERE Maps API for JavaScript within this environment is to install maps-api-for-javascript
NPM package which is hosted at https://repo.platform.here.com/. Add a registry entry to the NPM configuration by executing the following command:
npm config set @here:registry https://repo.platform.here.com/artifactory/api/npm/maps-api-for-javascript/
After that the package from the @here
namespace can be installed as usual:
npm install @here/maps-api-for-javascript --save
After that add the option "allowSyntheticDefaultImports": true
, under angularCompilerOptions
to tsconfig.json. At this step the environment setup is complete, all packages needed to build a sample application are installed, and it is possible to start the development server by executing:
ng serve
The command above launches the development server with the "hot reload" functionality. You can load the application by navigating to http://localhost:4200/
in the browser.
Add a static map component
It is possible to add a static map to the application by creating a component that contains an H.Map
instance and renders it to the component's container. First generate a component files with the help of CLI:
ng generate component jsmap
The command adds a jsmap
folder under the src/app
. The folder contains all the files needed to build the component:
└── src
├── app
│ ├── jsmap
| | ├── jsmap.component.css
| | ├── jsmap.component.html
| | ├── jsmap.component.spec.ts
| | └── jsmap.component.ts
│ ├── app-routing.module.ts
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ └── app.module.ts
...
Add the following code to the jsmap.component.ts
:
import { Component, ViewChild, ElementRef } from '@angular/core';
import H from '@here/maps-api-for-javascript';
@Component({
selector: 'app-jsmap',
templateUrl: './jsmap.component.html',
styleUrls: ['./jsmap.component.css']
})
export class JsmapComponent {
private map?: H.Map;
@ViewChild('map') mapDiv?: ElementRef;
ngAfterViewInit(): void {
if (!this.map && this.mapDiv) {
const platform = new H.service.Platform({
apikey: '{YOUR_API_KEY}'
});
const layers = platform.createDefaultLayers();
const map = new H.Map(
this.mapDiv.nativeElement,
layers.vector.normal.map,
{
pixelRatio: window.devicePixelRatio,
center: {lat: 0, lng: 0},
zoom: 2,
},
);
this.map = map;
}
}
}
The code above imports the HERE Maps API for JavaScript library and instantiates the map in the ngAfterViewInit
hook. Additionally replace the content of the jsmap.component.html
with
<div #map id="map"></div>
and add the following style rule to jsmap.component.css
:
#map {
width: 300px;
height: 300px;
}
The component above now can be used within the root App
component, replace the content of the app.component.html
with the following code:
<app-jsmap></app-jsmap>
That will render the static map at the zoom level 2 and 0 latitude and longitude in the 300 by 300 pixels viewport: 
Resizing the map
In many cases it is desirable that the map occupies the full width and/or height of the component. The H.Map
instance does not attempt to deduce when the parent container is resized and the map needs an explicit resize()
method call in order to adjust to the new dimensions of the container. To demonstrate how it can be achieved within the component we will use the simple-element-resize-detector
. Run the following command from the project's directory:
npm install simple-element-resize-detector --save
npm install @types/simple-element-resize-detector --save-dev
In the src/jsmap/jsmap.component.ts
adjust the import statements by importing the simple-element-resize-detector
library:
import { Component, ViewChild, ElementRef } from '@angular/core';
import H from '@here/maps-api-for-javascript';
import onResize from 'simple-element-resize-detector';
Update ngAfterViewInit
method with the map.getViewPort().resize()
:
ngAfterViewInit(): void {
if (!this.map && this.mapDiv) {
const platform = new H.service.Platform({
apikey: '{YOUR_API_KEY}'
});
const layers = platform.createDefaultLayers();
const map = new H.Map(
this.mapDiv.nativeElement,
layers.vector.normal.map,
{
pixelRatio: window.devicePixelRatio,
center: {lat: 0, lng: 0},
zoom: 2,
},
);
onResize(this.mapDiv.nativeElement, () => {
map.getViewPort().resize();
});
this.map = map;
}
}
Change the container style in jsmap.component.css
to:
#map {
width: 100%;
height: 300px;
}
the component will assume the width of the enclosing container: 
Setting the zoom and center
We want another component to take a user's input and change the zoom level and the center of the map. Create the new component by running Angular CLI:
ng generate component mapposition
The mapposition
component (src/app/mapposition/mapposition.component.html
) has three input fields: zoom, latitude and longitude. The template below introduces a change
event listener that redispatches events to the parent component:
<div>
Zoom:
<input
(change)="notify.emit($event)"
name="zoom"
type="number"
value="2"
/>
</div>
<div>
Latitude:
<input
(change)="notify.emit($event)"
name="lat"
type="number"
value="0"
/>
</div>
<div>
Longitude:
<input
(change)="notify.emit($event)"
name="lng"
type="number"
value="0"
/>
</div>
The TypeScript part of the component (src/app/mapposition/mapposition.component.ts
) uses the @Output
decorator and EventEmitter
class to notify the parent component about the changes in the user input:
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-mapposition',
templateUrl: './mapposition.component.html',
styleUrls: ['./mapposition.component.css']
})
export class MappositionComponent {
@Output() notify = new EventEmitter();
}
We will use the parent src/app
component to pass the values between the map and input fields. First adjust the template file src/app/app.component.html
to include the "input" and "output" values:
<app-jsmap
[zoom]="zoom"
[lat]="lat"
[lng]="lng"
></app-jsmap>
<app-mapposition
(notify)="handleInputChange($event)"
></app-mapposition>
Add the change event handler to the src/app/app.component.ts
that handler will update the values according to the user's input and Angular will take care of passing these values to the jsmap
component:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
title = 'jsapi-angular';
constructor() {
this.zoom = 2;
this.lat = 0;
this.lng = 0;
}
zoom: number;
lat: number;
lng: number;
handleInputChange(event: Event) {
const target = <HTMLInputElement> event.target;
if (target) {
if (target.name === 'zoom') {
this.zoom = parseFloat(target.value);
}
if (target.name === 'lat') {
this.lat = parseFloat(target.value);
}
if (target.name === 'lng') {
this.lng = parseFloat(target.value);
}
}
}
}
The final step is to adjust the jsmap
component by adding the ngOnChanges
hook and @Input
decorator properties for the latitude, longitude and the zoom level:
@Input() public zoom = 2;
@Input() public lat = 0;
@Input() public lng = 0;
ngOnChanges(changes: SimpleChanges) {
if (this.map) {
if (changes.zoom !== undefined) {
this.map.setZoom(changes.zoom.currentValue);
}
if (changes.lat !== undefined) {
this.map.setCenter({lat: changes.lat.currentValue, lng: this.lng});
}
if (changes.lng !== undefined) {
this.map.setCenter({lat: this.lat, lng: changes.lng.currentValue});
}
}
}
And adjust the import statement:
import { Component, ViewChild, ElementRef, Input, SimpleChanges } from '@angular/core';
The resulting application can take the input from the user with the help of the mapposition
component, store the state in the app
and update the jsmap
as per the user input: 
The interactive map
The application above takes only the input via the mapposition
component, normally users expect the map itself to be interactive. The optimal solution enables the user to input the values directly and see the desired location on the map, as well as interact with the map and see the current coordinates. That can be achieved by adding the mapviewchange
listener to the H.Map
instance and updating the app
state with the help of the EventEmitter
. In order to achieve that add a method in app.component.ts
that will be responsible to update the App
state:
handleMapChange(event: H.map.ChangeEvent) {
if (event.newValue.lookAt) {
const lookAt = event.newValue.lookAt;
this.zoom = lookAt.zoom;
this.lat = lookAt.position.lat;
this.lng = lookAt.position.lng;
}
}
Reflect the changes in the app.component.html
template by replaceing the current content with the following code:
<app-jsmap
[zoom]="zoom"
[lat]="lat"
[lng]="lng"
(notify)="handleMapChange($event)"
></app-jsmap>
<app-mapposition
[zoom]="zoom"
[lat]="lat"
[lng]="lng"
(notify)="handleInputChange($event)"
></app-mapposition>
Additionally in the jsmap
component import Output
and EventEmitter
classes:
import { Component, ViewChild, ElementRef, Input, SimpleChanges, Output, EventEmitter } from '@angular/core';
Add the following lines to attach a listener and enable the interactive behaviour after the map instantiation in the ngAfterViewInit
:
map.addEventListener('mapviewchange', (ev: H.map.ChangeEvent) => {
this.notify.emit(ev)
});
new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
Update the ngOnChanges
method to throttle the the unnecessary map updates:
private timeoutHandle: any;
@Output() notify = new EventEmitter();
ngOnChanges(changes: SimpleChanges) {
clearTimeout(this.timeoutHandle);
this.timeoutHandle = setTimeout(() => {
if (this.map) {
if (changes.zoom !== undefined) {
this.map.setZoom(changes.zoom.currentValue);
}
if (changes.lat !== undefined) {
this.map.setCenter({lat: changes.lat.currentValue, lng: this.lng});
}
if (changes.lng !== undefined) {
this.map.setCenter({lat: this.lat, lng: changes.lng.currentValue});
}
}
}, 100);
}
The last step is to adjust mapposition
component in such a way, that it reflects the current position of the map. In order to achieve that the Input
decorator must be imported from the @angular/core
in the mapposition.component.ts
:
import { Component, Output, EventEmitter, Input } from '@angular/core';
Afterwards the three input parameters must be added in the class body:
@Input() public zoom = 2;
@Input() public lat = 0;
@Input() public lng = 0;
The template mapposition.component.ts
must be adjusted accordingly:
<div>
Zoom:
<input
(change)="notify.emit($event)"
name="zoom"
type="number"
[value]="zoom"
/>
</div>
<div>
Latitude:
<input
(change)="notify.emit($event)"
name="lat"
type="number"
[value]="lat"
/>
</div>
<div>
Longitude:
<input
(change)="notify.emit($event)"
name="lng"
type="number"
[value]="lng"
/>
</div>
The resulting application consist of the interactive map and input fields. When the user interacts with the map the input fields are updated and vice versa.