Angular 2, child component using with google maps events losing context - javascript

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);
});

Related

Leaflet marker not appearing when using observables

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.

HERE maps in angular show a blank map

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:

Using Tippy.js within an Angular component [duplicate]

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();
}

Angular 2/4 Bing Map pushpin custom template function call

I have problem to fit my pushpin custom template into angular component.
My component:
import { Component, AfterViewInit } from '#angular/core';
#Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.scss']
})
export class MapComponent implements, AfterViewInit {
#ViewChild('myMap') myMap;
map: any;
infoboxClick;
infoboxTemplate = `<div id="infoboxText" class="custom-infobox">
<span class ="close-sighn" onclick="closeCustomInfoBox()">x</span>
{description}
</div>`;
constructor(private dataService: MapService) {
}
ngAfterViewInit() {
this.getMap();
}
getMap() {
if ((window as any).Microsoft && (window as any).Microsoft.Maps) {
this.map = new (window as any).Microsoft.Maps.Map(document.getElementById('mapId'), {
credentials: ''
});
var pushpin = new (window as any).Microsoft.Maps.Pushpin(map.getCenter(), null);
(window as any).Microsoft.Maps.Events.addHandler(pushpin, 'click', (args) => {
this.infoboxClick.setOptions({
location: args.target.getLocation(),
htmlContent: this.infoboxTemplate.replace('{description}', 'Some test description'),
visible: true
});
});
map.entities.push(pushpin);
} else {
setTimeout(() => { this.getMap() }, 1000);
}
}
closeCustomInfoBox() {
this.infoboxClick.setOptions({
visible: false
});
}
}
My view:
<div #myMap id="mapId" class="map-container"></div>
In infoboxTemplate I have function 'closeCustomInfoBox()', which should close infobox.
1) How I can call that function from my angular component?
2) I need to get proper angular scope if I call it how I can get approach to my 'infoboxClick' variables?
If you mean you want to access closeCustomInfoBox() function and infoboxClick variable from the parent component.
Check this
https://angular.io/guide/component-interaction#parent-interacts-with-child-via-local-variable

Angular 2-RC4 Dynamic Content Loading

Currently, I'm trying incorporating Leaflet into Angular 2-RC4. I have faced with the following problem of dynamically loading html into popupContent of leaflet markers.
In the parent class I have the following code:
export class BeaconLayerComponent implements OnInit {
constructor(private resolver: ComponentResolver, private viewRef: ViewContainerRef) {}
createMultipleDynamicInstance(markers) {
markers.foreach((marker) => {
createDynamicInstance(marker);
}
}
createDynamicInstance() {
this.resolver.resolveComponent(BeaconPopupComponent).then((factory:ComponentFactory<BeaconPopupComponent>) => {
let popupComponentRef: ComponentRef<BeaconPopupComponent>;
popupComponentRef = this.viewRef.createComponent(factory);
popupComponentRef.instance.name = beaconJSON.name;
popupComponentRef.instance.afterViewCheckedEventEmitter.subscribe((popupInnerHTML: string) => {
//make use of the innerHTML
// ... <= Create the leaflet marker with innerHTML as popupContent
//After retrieving the HTML, destroy the element
popupComponentRef.destroy();
})
});
Child component:
import {Component, AfterContentInit, EventEmitter, AfterViewInit, AfterViewChecked, ElementRef} from "#angular/core";
#Component({
selector: 'beaconPopup',
template: `
<div>{{name}}</div>
`
})
export class BeaconPopupComponent implements AfterViewChecked {
constructor(private elementRef: ElementRef) {}
name:string;
public afterViewCheckedEventEmitter = new EventEmitter();
ngAfterViewChecked() {
console.log("ngAfterViewChecked()");
this.afterViewCheckedEventEmitter.emit(this.elementRef.nativeElement.innerHTML);
}
}
When I run the html I get these errors:
2016-07-19 00:02:29.375 platform-browser.umd.js:1900 Error: Expression has changed after it was checked. Previous value: '[object Object]'. Current value: 'Happening Beacon'
at ExpressionChangedAfterItHasBeenCheckedException.BaseException [as constructor]
I'm trying to avoid getting the DOM element via JQuery
getDynamicElement(name)
{
str = "<div><div>" + name + "<div></div>"
return $(str).get[0]
}
Is there a better way to do it in Angular 2?
These are two tricks inside your code, 1 dynamical load, 2 how to use jQuery($)
Here is how I create dynamica component from string
compileToComponent(template1: string): Promise<ComponentFactory<any>> {
const metadata = new ComponentMetadata({
template: template1,
directives: ROUTER_DIRECTIVES
});
let decoratedCmp = Component(metadata)(class DynamicComponent { });
return this.resolver.resolveComponent(decoratedCmp);
}
For more details check here
https://github.com/Longfld/DynamicRouter_rc4/blob/master/app/MyRouterLink.ts
or demo here http://plnkr.co/edit/diZgQ8wttafb4xE6ZUFv?p=preview
For rc5 see here :http://plnkr.co/edit/nm5m7N?p=preview

Categories