Reputation: 1436
I'm using the HERE Maps Javascript API to display a map in my Angular Website. Most of the time it's working fine, but sometimes I'll get an error and the map does not render.
This is the error:
mapsjs-core.js:369 Tangram [error]: Error for style group 'non-collision' for tile 15/9/263/170/9 Cannot read property 'retain' of undefined: TypeError: Cannot read property 'retain' of undefined
at https://js.api.here.com/v3/3.1/mapsjs-core.js:369:259934
at Array.forEach (<anonymous>)
at https://js.api.here.com/v3/3.1/mapsjs-core.js:369:259905
at ZoneDelegate.invoke (http://localhost:4200/polyfills.js:3365:26)
at Object.onInvoke (http://localhost:4200/vendor.js:82817:33)
at ZoneDelegate.invoke (http://localhost:4200/polyfills.js:3364:52)
at Zone.run (http://localhost:4200/polyfills.js:3130:43)
at http://localhost:4200/polyfills.js:3861:36
at ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:3397:31)
at Object.onInvokeTask (http://localhost:4200/vendor.js:82798:33)
Jr @ mapsjs-core.js:369
This error seems to be random: If I just reload the page without making any changes, it will fail about 1 out of 10 times. The error can only be resolved with another page reload.
This is how I add the map:
this.map = new H.Map(this.mapElement.nativeElement, tiles, {
pixelRatio: window.devicePixelRatio || 1,
center: { lat: this.lat, lng: this.lng },
});
const behavior = new H.mapevents.Behavior(
new H.mapevents.MapEvents(this.map)
);
this.ui = H.ui.UI.createDefault(this.map, defaultLayers);
this.ui.getControl('mapsettings').setVisibility(false);
I'm using both clustering and markers. I've tried to disable both of them, but the error still randomly appears.
Does anybody have any idea? If you need more infos, please let me know.
Upvotes: 2
Views: 2456
Reputation: 69
It seems your question mostly related to Angular and not to HERE maps.
Correct, all HERE libraries/scripts should be loaded before. But not only! Your DOM elements which are used for map representation should be loaded before too!
In JavaScript exists event window.onload
But because you are using Angular framework the window.onload
sometimes will not work correctly together with Angular (digression: Who is to blame? Angular? JavaScript?, or is the Angular always right? haha...)
Therefore you need to use the Angular component lifecycle to manage the loading and updating of components:
Here is the sequence of Angular lifecycle hook calls:
constructor()
: The class constructor is called when an instance of the class is created. In Angular, the constructor is typically used for dependency injection.
ngOnChanges()
: It's called when changes occur in the input properties of the component. This method can be called multiple times during the component's lifecycle, whenever Angular detects changes in the input properties.
ngOnInit()
: It's called once after the component's properties are initially set. This is where initialization logic is typically placed.
ngDoCheck()
: It's called after ngOnInit()
and whenever Angular runs change detection. It can be used to implement custom change detection logic.
ngAfterContentInit()
: It's called once after the external content has been projected into the component's view.
ngAfterContentChecked()
: It's called after ngAfterContentInit()
and then whenever Angular checks the content projected into the component.
ngAfterViewInit()
: It's called once after the component's view and its child components have been initialized.
ngAfterViewChecked()
: It's called after ngAfterViewInit()
and then whenever Angular checks the component's view and its child components.
ngOnDestroy()
: It's called just before the component or directive is destroyed. It's useful for cleaning up resources and canceling subscriptions.
Therefore, ngAfterViewInit()
will always be called after ngOnInit()
, giving you the opportunity to interact with the DOM elements after they have been fully initialized.
Here's the execution order of the main Angular lifecycle stages:
ngOnChanges
: triggers following the modification of @Input
bound class members. It also fires upon initialization of input data.ngOnInit
: triggers once upon initialization of a component’s input-bound (@Input
) properties. The hook does not fire as the component receives the input data. Rather, it fires right after the data renders to the component template.ngDoCheck
: fires with every change detection cycle. Angular runs change detection frequently. Any action will cause it to cycle.Additionally please keep this guide on https://developer.here.com/documentation/maps/3.1.42.0/dev_guide/topics/angular-practices.html
Important steps from there:
npm config set @here:registry https://repo.platform.here.com/artifactory/api/npm/maps-api-for-javascript/
npm install @here/maps-api-for-javascript
Navigate to the jsapi-angular folder, and then open the tsconfig.json file in a preferred text editor.
In the tsconfig.json file, add the following setting under angularCompilerOptions:
"allowSyntheticDefaultImports": true
Then your code of component should be like (there is using ngAfterViewInit
):
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) {
// Instantiate a platform, default layers and a map as usual.
const platform = new H.service.Platform({
apikey: '{YOUR_API_KEY}'
});
const layers = platform.createDefaultLayers();
const map = new H.Map(
this.mapDiv.nativeElement,
// Add type assertion to the layers object...
// ...to avoid any Type errors during compilation.
(layers as any).vector.normal.map,
{
pixelRatio: window.devicePixelRatio,
center: {lat: 0, lng: 0},
zoom: 2,
},
);
this.map = map;
}
}
}
Add to your project simple-element-resize-detector then you can resize a map within an Angular component.
Enter the following commands :
npm install simple-element-resize-detector --save
npm install @types/simple-element-resize-detector --save-dev
Then in your code component:
import { Component, ViewChild, ElementRef } from '@angular/core';
import H from '@here/maps-api-for-javascript';
import onResize from 'simple-element-resize-detector';
And:
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 as any).vector.normal.map,
{
pixelRatio: window.devicePixelRatio,
center: {lat: 0, lng: 0},
zoom: 2,
},
);
onResize(this.mapDiv.nativeElement, () => {
map.getViewPort().resize();
});
this.map = map;
}
}
Upvotes: -1
Reputation:
This might be due to some functions being called before all HERE libraries/scripts are loaded. So make sure the HERE external scripts are fully loaded and the window
object is available before instantiating map objects and calling map functions.
Upvotes: 1