even when I use the exact example of here map official site I received a blank view, only with HERE logo and the rest of this layer.
I received a "WebGL warning: uniform setter: No active linked Program." warning on my firefox and VSC debug console.
I changed the navigator to Chrome and Opera, and I received the same result.
My apikey is alright, I test it on postman and it worked.
This my code
import { Component, ElementRef, OnInit, ViewChild, Input } from '#angular/core';
declare var H: any;
#Component({
selector: 'here-map',
templateUrl: './heremap.component.html',
styleUrls: ['./heremap.component.scss'],
})
export class HeremapComponent implements OnInit {
#ViewChild("map") public mapElement: ElementRef;
#Input() private _apikey: any;
#Input() public lat: any;
#Input() public lng: any;
#Input() public width: any;
#Input() public height: any;
private map: any;
constructor() { }
ngOnInit() {}
public ngAfterViewInit() {
console.log('HERE apikey: ', this._apikey);
let platform = new H.service.Platform({
apikey: this._apikey
});
let defaultLayers = platform.createDefaultLayers();
this.map = new H.Map(this.mapElement.nativeElement,
defaultLayers.vector.normal.map,
{
zoom: 10,
center: { lat: this.lat, lng: this.lng }
}
);
this.map.setCenter({lat: 52.5159, lng: 13.3777});
this.map.setZoom(14);
}
}
<div class="here-maps" #map [style.width]="width" [style.height]="height"></div>
I was having the same issue, and I fixed it by resizing the map after it was fully loaded.
You must trigger the resize event after 500ms (or something like that) at the end of the ngAfterViewInit() event:
This is an example of my code:
setTimeout(function () {
window.dispatchEvent(new Event('resize'));
}, 500);
In the resize event, you should resize the map:
#HostListener('window:resize', ['$event'])
onResize(event: any) {
requestAnimationFrame(() => this.map.getViewPort().resize());
}
After this, all is working as expected:
Related
map.component.ts
export class MapComponent implements AfterViewInit {
private map;
private centroid: L.LatLngExpression = [49.2827, -123.1207];
private initMap(): void {
this.map = L.map('map', {
center: this.centroid,
zoom: 12
});
const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
minZoom: 3,
attribution: '© OpenStreetMap'
});
tiles.addTo(this.map);
}
constructor(private _dataService: DataService) {
}
ngAfterViewInit(): void {
this.initMap();
this._dataService.pigMessage$.subscribe(message => {
L.marker([49.2827, -123.1207]).addTo(this.map).bindPopup("test").openPopup();
console.log(message);
})
}
}
data.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
import { pigReportType } from './pigReport';
#Injectable({
providedIn: 'root'
})
export class DataService {
private _pigMessageSource = new Subject<pigReportType>();
pigMessage$ = this._pigMessageSource.asObservable();
sendPigData(message: pigReportType){
this._pigMessageSource.next(message)
}
constructor() { }
}
When I click on the submit button, data service sends a new piece of information into the observable and redirects back to the map component. The problem is the marker is not adding inside of the subscribe function in map.component.ts.
Additionally, I have checked that the .subscribe function works because it prints the correct message. The markers just do not appear on the map.
I have tried looking through the html and seeing if there is a duplicated map that is covering the map with the marker but there isn't. Also I have tried to call initMap inside of the .subscribe function but it doesn't work.
I'm hoping if someone can point me in the right direction because I have searched everywhere on the web for a solution but can't find it.
While integrating the WebDataRocks in angular for pivot table, I am getting the error
Property 'next' does not exist on type 'EventEmitter<CellData>'
Here is my code,
import { Component, ElementRef, Input, Output, EventEmitter, OnInit } from '#angular/core';
import * as WebDataRocks from 'webdatarocks';
#Component({
selector: 'app-wbr-pivot',
template: `<div><div class='wbr-ng-wrapper'></div></div>`
})
export class WebdatarocksComponent implements OnInit{
// params
#Input() toolbar: boolean;
#Input() width: string | number;
#Input() height: string | number;
#Input() report: WebDataRocks.Report | string;
#Input() global: WebDataRocks.Report;
#Input() customizeCell: (cell: WebDataRocks.CellBuilder, data: WebDataRocks.CellData) => void;
// events
#Output() cellclick: EventEmitter<WebDataRocks.CellData> = new EventEmitter();
// api
public webDataRocks: WebDataRocks.Pivot;
// private
private root: HTMLElement;
constructor(private el: ElementRef) { }
ngOnInit() {
this.root = this.el.nativeElement as HTMLElement;
this.webDataRocks = new WebDataRocks({
container: this.root.getElementsByClassName('wbr-ng-wrapper')[0],
width: this.width,
height: this.height,
toolbar: this.toolbar,
report: this.report,
global: this.global,
customizeCell: this.customizeCell,
cellclick: (cell: WebDataRocks.CellData) => this.cellclick.next(cell)
});
}
}
Can't reproduce this with the same code on my side. Have you changed anything else in the sample project perhaps?
EventEmitter's next method is deprecated in favor of emit
this.cellclick.emit(cell)
I am looking at ArcGIS Javascript API 4.18.1. But it is confusing, how I should add it to a new Angular 11 project. Is there an example project somewhere that shows the basic folder structure and getting a map setup in Angular 11? I want to get it set up with just ES modules using NPM.
https://developers.arcgis.com/javascript/latest/es-modules/
I did this:
npm install #arcgis/core
Then I added the following to app.component.ts
import WebMap from '#arcgis/core/WebMap';
import MapView from '#arcgis/core/views/MapView';
I am new to this. And It seems like all the documentation talks about React.
Then in the getting started with Map it has you enter a key:
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>ArcGIS API for JavaScript Tutorials: Display a map</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.18/esri/themes/light/main.css">
<script src="https://js.arcgis.com/4.18/"></script>
<script>
require(["esri/config","esri/Map", "esri/views/MapView"], function (esriConfig,Map, MapView) {
esriConfig.apiKey = "YOUR-API-KEY";
const map = new Map({
basemap: "arcgis-topographic" // Basemap layer service
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>
But these docs are not for Typescript. Do they have typescript docs somewhere? How do you add the Key with the new API 4.18.1 and Typescript and NPM and Angular 11?
This is what I came up with.
It is based off https://github.com/TheKeithStewart/angular-esri-components which uses the arcgis-js-api library
https://github.com/Esri/jsapi-resources/tree/master/esm-samples/jsapi-angular-cli has the basic setup instructions for using #arcgis/core in an Angular application
esri-map.component.scss:
#mapViewNode {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
esri-map.component.ts:
import {
Component,
ElementRef,
Input,
OnInit,
NgZone,
OnDestroy,
ViewChild,
Output,
EventEmitter,
} from '#angular/core';
import config from '#arcgis/core/config';
import { EsriMapService } from './esri-map.service';
#Component({
// tslint:disable-next-line: component-selector
selector: 'esri-map',
template: '<div #mapViewNode></div>',
styleUrls: ['./esri-map.component.scss'],
})
export class EsriMapComponent implements OnInit, OnDestroy {
#ViewChild('mapViewNode', { static: true }) private elementRef!: ElementRef;
#Input() mapProperties!: any;
#Input() mapViewProperties!: any;
#Output() mapInit: EventEmitter<any> = new EventEmitter();
private mapView: any;
constructor(private zone: NgZone, private mapService: EsriMapService) {}
ngOnInit(): void {
config.assetsPath = 'assets/';
this.zone.runOutsideAngular(() => {
this.loadMap();
});
}
ngOnDestroy(): void {
this.mapView.destroy();
}
loadMap(): void {
this.mapService.isLoaded.subscribe((n: any) => {
this.mapView = n.view;
this.zone.run(() => {
this.mapInit.emit({ map: n.map, view: n.view });
this.mapInit.complete();
});
});
this.mapService.loadWebMap({
...this.mapProperties,
...this.mapViewProperties,
container: this.elementRef.nativeElement,
});
}
esri-map.service.ts:
import { EventEmitter, Injectable } from '#angular/core';
import MapView from '#arcgis/core/views/MapView';
import WebMap from '#arcgis/core/WebMap';
import Widget from '#arcgis/core/widgets/Widget';
import Layer from '#arcgis/core/layers/Layer';
export type Position =
| 'bottom-leading'
| 'bottom-left'
| 'bottom-right'
| 'bottom-trailing'
| 'top-leading'
| 'top-left'
| 'top-right'
| 'top-trailing'
| 'manual';
#Injectable({
providedIn: 'root',
})
export class EsriMapService {
map!: WebMap;
view!: MapView;
loaded = false;
isLoaded = new EventEmitter();
constructor() {}
loadWebMap(props: {
basemap: any;
container: any;
center: any;
zoom: any;
}): void {
this.map = new WebMap({ basemap: props.basemap });
this.view = new MapView({
container: props.container,
map: this.map,
center: props.center,
zoom: props.zoom,
});
this.loaded = true;
this.isLoaded.emit({
map: this.map,
view: this.view,
});
}
addLayer(layer: Layer, clearLayers?: boolean): void {
if (clearLayers) {
this.view.map.removeAll();
}
this.view.map.add(layer);
}
addWidget(
component: string | HTMLElement | Widget | any[],
position?: Position,
): void {
this.view.ui.add(component, position);
}
}
You would just instantiate the component like this:
<esri-map [mapProperties]="mapProperties" [mapViewProperties]="mapViewProperties" (mapInit)="onMapInit($event)">
</esri-map>
mapProperties sets the base map.
mapViewProperties sets the center and zoom.
mapInit lets you know when the map is loaded.
The service provides the map view for constructing the map.
You would then add your layers to the map view as usual.
I have a directive with the following code
import { Directive, Input, OnInit, ElementRef, SimpleChanges, OnChanges } from '#angular/core';
import tippy from 'tippy.js';
#Directive({
selector: '[tippy]'
})
export class TippyDirective implements OnInit, OnChanges {
#Input('tippyOptions') public tippyOptions: Object;
private el: any;
private tippy: any = null;
private popper: any = null;
constructor(el: ElementRef) {
this.el = el;
}
public ngOnInit() {
this.loadTippy();
}
public ngOnChanges(changes: SimpleChanges) {
if (changes.tippyOptions) {
this.tippyOptions = changes.tippyOptions.currentValue;
this.loadTippy();
}
}
public tippyClose() {
this.loadTippy();
}
private loadTippy() {
setTimeout(() => {
let el = this.el.nativeElement;
let tippyOptions = this.tippyOptions || {};
if (this.tippy) {
this.tippy.destroyAll(this.popper);
}
this.tippy = tippy(el, tippyOptions, true);
this.popper = this.tippy.getPopperElement(el);
});
}
}
And using the directive as follows
<input tippy [tippyOptions]="{
arrow: true,
createPopperInstanceOnInit: true
}" class="search-input" type="text"
(keyup)="searchInputKeyDown($event)">
How can I have the Tippy shown on mouseenter or focus as these are the default triggers, from the tippy instance I have in the directive, this is what I get when I put console.log(this.tippy) on line 44
{
destroyAll:ƒ destroyAll()
options:{placement: "top", livePlacement: true, trigger: "mouseenter focus", animation: "shift-away", html: false, …}
selector:input.search-input
tooltips:[]
}
As I am getting an error when I try to use
this.popper = this.tippy.getPopperElement(el);
ERROR TypeError: _this.tippy.getPopperElement is not a function
How can I get this directive to work as I took it from a repo in github
https://github.com/tdanielcox/ngx-tippy/blob/master/lib/tippy.directive.ts
What is it that I am missing here, any help is appreciated, thanks
I'm not sure what they were trying to accomplish in the linked repo you have included. To get tippy.js to work though, you should be able to change the directive to the following:
import { Directive, Input, OnInit, ElementRef } from '#angular/core';
import tippy from 'tippy.js';
#Directive({
/* tslint:disable-next-line */
selector: '[tippy]'
})
export class TippyDirective implements OnInit {
#Input('tippyOptions') public tippyOptions: Object;
constructor(private el: ElementRef) {
this.el = el;
}
public ngOnInit() {
tippy(this.el.nativeElement, this.tippyOptions || {}, true);
}
}
Working example repo
This works with tippy.js 6.x
#Directive({selector: '[tooltip],[tooltipOptions]'})
export class TooltipDirective implements OnDestroy, AfterViewInit, OnChanges {
constructor(private readonly el: ElementRef) {}
private instance: Instance<Props> = null;
#Input() tooltip: string;
#Input() tooltipOptions: Partial<Props>;
ngAfterViewInit() {
this.instance = tippy(this.el.nativeElement as Element, {});
this.updateProps({
...(this.tooltipOptions ?? {}),
content: this.tooltip,
});
}
ngOnDestroy() {
this.instance?.destroy();
this.instance = null;
}
ngOnChanges(changes: SimpleChanges) {
let props = {
...(this.tooltipOptions ?? {}),
content: this.tooltip,
};
if (changes.tooltipOptions) {
props = {...(changes.tooltipOptions.currentValue ?? {}), content: this.tooltip};
}
if (changes.tooltip) {
props.content = changes.tooltip.currentValue;
}
this.updateProps(props);
}
private updateProps(props: Partial<Props>) {
if (this.instance && !jsonEqual<any>(props, this.instance.props)) {
this.instance.setProps(this.normalizeOptions(props));
if (!props.content) {
this.instance.disable();
} else {
this.instance.enable();
}
}
}
private normalizeOptions = (props: Partial<Props>): Partial<Props> => ({
...(props || {}),
duration: props?.duration ?? [50, 50],
});
}
Using this looks like:
<button [tooltip]="'Hello!'">Hover here</button>
<button [tooltip]="'Hi!'" [tooltipOptions]="{placement: 'left'}">Hover here</button>
You can also use the lifecyle hook ngAfterViewInit then you don't need the setTimeout.
public ngAfterViewInit() {
this.loadTippy();
}
I have a case when I am using component in component. By accessing properties of child component via #ViewChild, I am getting undefined for methods in child component.
// Parent component
declare var google: any;
#Component({
selector: 'app-map',
templateUrl: `
<div id="map"></div>
<app-context-menu></app-context-menu>`,
styleUrls: ['./map.component.css'],
providers: [CitiesService],
})
export class MapComponent implements AfterViewInit {
#ViewChild(ContextMenuComponent) contextMenu: ContextMenuComponent;
user: UserInterface;
city: any;
map: any;
constructor(
private userStorage: UserStorage,
private citiesService: CitiesService,
private theMap: TheMap,
) {}
ngAfterViewInit() {
this.findUserCityCoords();
}
inittializeMap(mapOpts) {
let mapProps = {
center: new google.maps.LatLng(mapOpts.lat, mapOpts.lng),
zoom: mapOpts.zoom,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
let map = new google.maps.Map(document.getElementById('map'), mapProps);
let menu = this.contextMenu;
map.addListener('rightclick', (e) => {
console.log('map', this);
console.log('menu', this.contextMenu);
this.contextMenu.open(e);
});
}
findUserCityCoords() {
let userCity = this.userStorage.getFromStorage().city;
this.citiesService.getCityConfig()
.subscribe(cities => {
cities.forEach(city => {
if (city.name === userCity) this.inittializeMap(city.center);
});
});
}
From this class when 'map event listener' calls 'menu.open(e);' the context changes and inside child component, the methods of child component are not available. To call i.e. 'this.draw()' method.
// Child component
import { Component, OnInit, ElementRef, AfterViewInit } from
'#angular/core';
import { TheMap } from '../../services/mapServices/TheMap';
import { Marker } from '../../services/mapServices/marker.service';
declare var google: any;
#Component({
selector: 'app-context-menu',
templateUrl: './context-menu.component.html',
styleUrls: ['./context-menu.component.css']
})
export class ContextMenuComponent {
overlay;
el;
constructor(
private theMap: TheMap,
private marker: Marker,
private elementRef: ElementRef
) {
this.overlay = new google.maps.OverlayView();
}
ngAfterViewInit() {}
open(e) {
let map = this.theMap.getMap();
this.overlay.set('position', e.latLng);
this.overlay.setMap(map);
this.draw(); // <---- Here, this.draw() is not a function
// So I can access properties from constructor but not this.draw() and other methods of this class
};
draw() {
// ....
}
How can I properly implement google's map 'rightclick' event listener (probably with 'bind') to use my child's component methods. Thanks
Not sure I understand the question but () => (arrow function) might be what you're looking for
map.addListener('rightclick', (e) => {
console.log('map', this);
console.log('menu', menu);
this.contextMenu.open(e);
});