I am using this wrapper for the azure maps library. I am currently implementing a popup. When following the provided example, applied to my needs, I cannot get the code to work.
this is my component:
import {Component, Input, OnInit} from '#angular/core';
import * as atlas from 'azure-maps-control';
import {ILayerEvent} from 'ng-azure-maps';
#Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.scss']
})
export class MapComponent {
#Input() locations;
private markerImagePath = 'assets/images/map-marker.png';
public dataSource: atlas.source.DataSource;
public popupContent: string;
public popupPosition: atlas.data.Position;
public popupOpened = false;
public dwOptions: atlas.IconOptions = {
image: 'marker'
};
points = [
[52.52437, 13.41053],
[51.50853, -0.12574]
];
mapReady(map: atlas.Map) {
map.imageSprite.add('marker', this.markerImagePath).then(r => {
this.dataSource = new atlas.source.DataSource('markers');
this.points.forEach(p => {
const point = new atlas.Shape(new atlas.data.Point([p[1], p[0]]));
this.dataSource.add([point]);
});
});
}
onMouseEnter(e: ILayerEvent): void {
const point = e.event.shapes['0'].data.geometry.coordinates as [number, number];
this.showInfo(point);
}
onMouseLeave() {
this.closePopup();
}
showInfo(targetPoint: [number, number]): void {
this.popupPosition = new atlas.data.Position(targetPoint[0], targetPoint[1]);
this.popupOpened = true;
this.popupContent = 'Shows on mouse over';
}
closePopup(): void { this.popupOpened = false; }
}
this is my template:
<section>
<div class="row">
<div class="col-12 map-dimensions my-2 mx-auto" azure-map zoom="2"
[dataSources]="[dataSource]" (onReady)="mapReady($event.map)">
<map-symbol-layer dataSourceId="markers"
[iconOptions]="dwOptions"
(onMouseEnter)="onMouseEnter($event)"
(onMouseLeave)="onMouseLeave()"></map-symbol-layer>
<map-popup [content]="popupContent"
[position]="popupPosition"
[opened]="popupOpened"></map-popup>
</div>
</div>
</section>
I really cannot pin down the problem, since the variables are all set and also changed onMouseEnter and onMouseLeave
When it comes to mouse events on layers its good to understand at the layer level, not the feature level. This means that mouseenter event will only fire when the mouse initially moves over a feature in the layer and will not fire again until the mouse has left the layer and reentered. If the mouse moves from one feature to another within a layer without leaving the layer, the mouseenter event will not fire a second time. The mouseleave event works in a similar way. If you want a mouse event that fires as you move the mouse from feature to feature, use the mousemove event instead of the mouseenter event. For your scenario I would still use the mouseleave event.
Related
I am working KendoUi angular 5. I need How to select Multiple rows with out Using Ctrl key And check check boxes in kendogrid in angular 5?
I am able to Select Multiple rows using Ctrl key or select check boxes as per telerik kendo grid but i want to select multiple rows with out select check box or Ctrl key
You can use the rowSelected function and override the built-in Grid selection behavior, replacing it with a custom one:
DOCS
For example you can use the cellClick event handler where the data item, associated with the row the clicked cell is in, is available as event data and store the selected items in a collection that you can manipulate as necessary:
import { Component } from '#angular/core';
import { products } from './products';
import { RowArgs } from '#progress/kendo-angular-grid';
#Component({
selector: 'my-app',
template: `
<kendo-grid
[data]="gridData"
[height]="500"
[selectable]="true"
[rowSelected]="isRowSelected"
(cellClick)="onCellClick($event)"
>
<kendo-grid-column field="ProductName" title="Product Name" [width]="300"></kendo-grid-column>
<kendo-grid-column field="UnitsInStock" title="Units In Stock"></kendo-grid-column>
<kendo-grid-column field="UnitsOnOrder" title="Units On Order"></kendo-grid-column>
<kendo-grid-column field="ReorderLevel" title="Reorder Level"></kendo-grid-column>
</kendo-grid>
`
})
export class AppComponent {
public gridData: any[] = products;
public mySelection: any[] = [];
public onCellClick({dataItem}) {
const idx = this.mySelection.indexOf(dataItem.ProductID);
if(idx > -1) {
// item is selected, remove it
this.mySelection.splice(idx, 1);
} else {
// add item to the selection
this.mySelection.push(dataItem.ProductID);
}
}
// Use an arrow function to capture the 'this' execution context of the class.
public isRowSelected = (e: RowArgs) => this.mySelection.indexOf(e.dataItem.ProductID) >= 0;
}
PLUNKER EXAMPLE
I found an alternate workaround which doesn't require rebuilding the row selection, which I prefer as it feels less risky.
All this does is hijacks any click event on a row in the grid, hides it from Kendo, and then sends a fake click event with ctrl down instead; therefore causing Kendo to do the proper selection behaviour for us.
It must be done in databind as when the page is changed or a filter added the event needs to be bound to the new rows.
$("#yourGrid").kendoGrid({
...
dataBound: dataBound
...
});
...
function dataBound() {
$(".k-master-row").click(function (e) {
// Prevent stack overflow by exiting if we have already done the below logic
// and let kendo handle it
if (e.ctrlKey === true)
return;
// Prevent Kendo from handling this click event as we are going to send another one with ctrl this time
e.preventDefault();
e.stopPropagation();
// Create a fake click with ctrl pressed on this particular row (Kendo will keep previous rows selected in this case)
var e2 = jQuery.Event("click");
e2.ctrlKey = true; // Ctrl key pressed
$(this).trigger(e2);
});
}
I am trying to trigger a custom function inside of my columnDef cell template as follows:
export class GridComponent {
constructor() {}
this.gridOptions = {
// grid options defined here
columnDef.cellTemplate = '<bss-cell cellval="{{row.entity[col.name]}}"></bss-cell>';
}
private cellClicked () {
// need to get the click event here but can't
console.log('got here');
}
}
This column definition holds the data in my grid. Bss Cell is a custom angular component I made which looks like this:
//BSS CELL CONTROLLER CODE FOR THE COLUMN DEF CELL TEMPLATE
import * as angular from 'angular';
import '../../../../modules/bss/bss.component';
export class TreeCellComponent {
constructor() {
}
private cellClicked () {
// click event does come here if I call $ctrl.cellClicked() from my template but this isn't connected to the grid
}
}
angular.module('app.modules.uigridtemplates.bss-cell', ['app.modules.bss'])
.component('bssCell', {
bindings: {
cellval: '#'
},
controller: TreeCellComponent,
template: require('./bss-cell.html')
});
// BSS CELL DIRECTIVE TEMPLATE
<div ng-click="grid.AppScope.cellClicked()" align="right" class="ui-grid-cell-contents-data-grid" title="
{{$ctrl.cellval}}">
<span>{{$ctrl.cellval}}</span>
</div>
How can I get that click to take place so that it runs to the "cellClicked" function inside of my GridComponent, which would then allow me to affect the grid the way I would like.
this.cellTemplates = ['uiGridTreeCellTemplate.html', 'uiGridTreeColumnHeaderTemplate.html'];
this.gridOptions = {
appScopeProvider: this
}
Inside your gridOptions add this:
appScopeProvider: {
cellClicked: this.cellClicked
}
In this way in you grid.appScope you will find the cellClicked function available.
If you have selectable rows, which perform an action when you select one of your rows, I suggest to pass the $event to your grid.appScope.cellClicked() function, then in your appScopeProvider stop the click event, something like this:
ng-click="grid.appScope.cellClicked($event)"
and then
appScopeProvider: {
cellClicked: (event) => {
this.cellClicked();
event.preventDefault();
event.stopPropagation();
}
}
I have a simple app component with a search input and an observable resultItems: Observable<Array<string>>; powered by a search service that returns results to the UI via *ngFor. There is also a leaflet map that should render the locations of the results. The search service works well and I can render the location of one result in the map onclick. My question is: What is the recommended way to call the map service mapResults every time the search service returns new results or the observable changes? I can imagine how I could create a custom pipe that would iterate over the parks in the service results and call mapservice.mapResult but that seems odd since the pipe wouldn't return anything to the UI and I'm a little concerned with performance, understanding little about pure and impure pipes. I have also seen mention of a process by which you subscribe to changes of an observable, but I'm lost with the semantics and changes among API versions.
I apologize if this is a problem of poor design. I only have a few weeks of Angular learning and I admittedly haven't read the documentation thoroughly. Please point any and all issues you see.
simple Search Service
import { Injectable } from '#angular/core';
import { URLSearchParams, Jsonp } from '#angular/http';
#Injectable()
export default class ParkSearchService {
constructor(private jsonp: Jsonp) { }
search(parkSearchterm: string) {
var search = new URLSearchParams()
search.set('q', 'PARK_NAME:*' + parkSearchterm+'*');
search.set('wt', 'json');
search.set('json.wrf','JSONP_CALLBACK')
var test = this.jsonp
.get('http://parksearch/parks_full/select?', { search })
.map((response) => response.json()['response']['docs']);
return test
}
}
exert from app.component.html
<md-card *ngFor="let item of resultItems | async; let i = index"
class="search-result"
[ngClass]="{ 'selected-result': selectedIndex === i }">
<md-card-header class="clickable"
(click)="showBoundary(item)"
md-tooltip="Zoom to park">
<md-card-title>{{item.PARK_NAME}}</md-card-title>
</md-card-header>
<md-card-content style="height: 75px; overflow-y: auto">
<button md-button
color="primary"
md-tooltip="more info"
(click)="openDtl(item.PARK_HOME_PAGE_URL)">
<md-icon>info</md-icon>
<span>Details...</span>
</button>
<button md-button
color="primary"
md-tooltip="open park website"
(click)="openParkURL(item.PARK_HOME_PAGE_URL)">
<md-icon>web</md-icon>
<span>WebSite</span>
</button>
Amenties: {{ item.AMEN_LIST }}
</md-card-content>
</md-card>
app.component.ts (forgot to include)
export class AppComponent {
private selectedIndex: number;
public events: any[] = [];
//park search items
resultItems: Observable<Array<string>>;
parkSearchterm = new FormControl();
//setup resultitems
ngOnInit() {
this.mapService.initialize();
this.resultItems = this.parkSearchterm.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(parkSearchterm => this.parkSearchService.search(parkSearchterm));
map service:
//Thanks for the help getting started https://github.com/haoliangyu
import { Injectable } from '#angular/core';
import { Map, GeoJSON } from 'leaflet';
#Injectable()
export default class MapService {
public map: Map;
private currentLayer: GeoJSON;
private resultsLayer: any;
private resultfeatureCollection: any;
constructor() {
}
initialize() {
if (this.map) {
return;
}
this.map = L.map('map', {
zoomControl: true,
zoom: 6,
minZoom: 3,
maxZoom: 19
});
L.tileLayer('http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap, Tiles courtesy of Humanitarian OpenStreetMap Team'
}).addTo(this.map);
L.control.scale().addTo(this.map);
//Add the results layer
this.resultsLayer = L.geoJSON(this.resultfeatureCollection, {
style: () => {
return {
color: '#ff00005',
fillColor: '#3F51B5'
};
}
}).addTo(this.map);
}
mapResults(park) {
//update the restults layer
let resultfeatureCollection: GeoJSON.FeatureCollection<any> = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: "Polygon",
coordinates: JSON.parse(park.BBOX[0])
},
properties: {
name: 'test'
}
}
]
};
this.resultsLayer.addData(resultfeatureCollection);
this.map.fitBounds(this.resultsLayer.getBounds());
}
}
You are very close to what you need: You already have an observable stream created for your results, called resultItems, that is right. Then on your template, when you use it through the async pipe what Angular does internally is subscribes to this stream to get its values.
So if you want to "also" map when resultItems yields is by subscribing to it by yourself as well. There's a catch though: by default every subscription duplicates the workload for the stream, meaning that every time the user makes a new search it would run the API call twice: 1 for the async subscription and another one for your .subscribe.
The way to resolve that is by using .publish(): What this allows is to share the result of an stream between many subscribers, so your code would look like this:
ngOnInit() {
this.mapService.initialize();
this.resultItems = this.parkSearchterm.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(parkSearchterm => this.parkSearchService.search(parkSearchterm))
.publish();
// With publish() we are sharing the items from this stream to all of their subscribers
// We just need to tell it to do the "first subscription"
this.resultConnection = this.resultItems.connect();
// This "resultConnection" is the base subscription... We will have to dispose it in our ngOnDestroy method or we might get memory leaks
// Now we can tell the map service to update when we get a value:
this.resultItems.subscribe((park) => this.mapService.mapResults(park));
// (Not sure if the data types are correct, I assume you can map them)
// When `async` calls .subscribe() to do his job, we won't get duplicate API calls thanks to .publish()
}
To clarify, what connect() does is subscribe to the original stream, and start forwarding the values recieved to the subscribers of the published stream (this.resultItems). Now you are the owner of that subscription, so you are responsible of disposing it whenever you don't need more searches.
Subscribe on observer and call then search is changed. https://angular.io/docs/ts/latest/guide/router.html#!#reuse
After get result from server, put it to array. Angular will update the data in the template itself, this is the fastest way.
This might be answered, but damn if I can find it . I am creating a module, and its working, the issue is I want to assign a property to another property on my module.
so in angular 2 (with ng-module) I have created a simple panel
<simple-panel name="MyPanel"></simple-panel>
I have it working great, the issue is I want to assign a property to the name Property now and I have no idea what is the best way to do this.
so I would like to return {{MyPanel.thisProperty}} for use on the page where I am calling the tag.
here is a sample of what I am doing, stripped down for this question
here is my simple-panel.ts
import {Component,NgModule,ModuleWithProviders, Directive, Input} from '#angular/core';
/**
* Content of the edit panel.
*/
#Directive({
selector: 'simple-panel-edit-panel-content'
})
export class SimplePanelEditPanelContent {}
#Component({
selector: 'simple-panel',
templateUrl: 'simple-panel.html',
styleUrls: ['simple-panel.css'],
encapsulation: ViewEncapsulation.None
})
export class SimplePanel{
private _name: string;
private _announceedit: boolean = false;
private _buttonname: string = 'edit';
/** This sets the Name as a variable that can be used. */
#Input()
get name(): string { return this._name; }
set name(value) { this._name = value; }
/**Sets the Edit Announcement */
#Input()
get editannounce(): boolean { return this._announceedit; }
set editannounce(value: boolean) {
if (!value) {
this._announceedit = true;
this._buttonname = 'search';
}else{
this._announceedit = false;
this._buttonname = 'edit';
}
}
}
#NgModule({
exports: [SimplePanel,SimplePanelEditPanelContent],
declarations: [SimplePanel,SimplePanelEditPanelContent],
})
export class SimplePanelComponent {
static forRoot(): ModuleWithProviders {
return {
ngModule: SimplePanelComponent,
providers: []
};
}
}
here is the simple-panel.html
<md-card>
<md-card-title-group>
<button md-raised-button (click)="editannounce=editannounce;"><md-icon>{{ _buttonname }}</md-icon></button>
</md-card-title-group>
<md-card-content>
<ng-content select="simple-panel-edit-panel-content"></ng-content>
</md-card-content>
<md-card-actions>
<button md-raised-button (click)="editannounce = editannounce"><md-icon>save</md-icon> SAVE</button>
</md-card-actions>
</md-card>
when someone uses the module, a panel is created with a button
when someone clicks the button I can access the variable within the template above, but what I want to do is actually access a variable that is used on the page itself where they call the module to use. it would be nice to have it named MyPanel.announceedit or MyPanel.editable as an example, but the main thing is that a variable is created, and watched, when it changes it passes it back up to where the module is bieng used and allows user the ability to access it within the content area, so if they added an input and wanted to see if the button was clicked to set the readOnly attribute they could. Hopefully this makes more sense.
If you write it like
<simple-panel [name]="MyPanel"></simple-panel>
in the component that includes this html, you can access/set MyPanel with a simple this.MyPanel.
And in your SimplePanel component
#Input() name;
...
this.name = "something";
is again all you need to set and get that field.
I have an Angular 2 component that displays a list of Items, and that registers to a service that publishes events. The problem is that even if I don't do anything when I receive an event, Angular updates the view (or at least do something when, I guess, it shouldn't).
Here is a plunker.
As you can see in the console, the "getTitle()" method of my items is called each time my service publishes a message.
Even if I don't register to my service and if my component doesn't implement the MyServiceListener interface, getTitle is called each time the service gets a message. If I don't give the service to my component in its constructor, everything is fine. So, something seems wrong with my dependency injection, but what?
Here is the relevant code of the plunker:
My service and its listeners interface:
export interface MyServiceListener {
onMessage(_message: any);
}
export class MyService {
private m_listener: MyServiceListener;
constructor() {
window.setInterval(() => {
if (this.m_listener !== undefined) {
this.m_listener.onMessage("Hi");
}
}, 500);
}
setListener(_listener: MyServiceListener) { this.m_listener = _listener; }
}
The Item class:
export class Item {
m_title: string;
constructor(_title: string) {
this.m_title = _title;
}
getTitle(): string { console.log("getTitle"); return this.m_title; }
}
My component:
#Component({
selector: 'my-app',
template : `
<div>
<ul>
<li *ng-for="#item of m_items">
{{item.getTitle()}}
</li>
</ul>
</div>
`
})
export class App implements TestBugAngularServiceListener {
private m_items: Array<Item> = new Array<Item>();
constructor(_communicationService: MyService) {
this.m_items.push(new Item("A"));
this.m_items.push(new Item("B"));
this.m_items.push(new Item("C"));
_communicationService.setListener(this);
}
onMessage(_message: any) {
}
}
bootstrap(App, [MyService]).catch(err => console.error(err));
Both articles : Change detection and Angular immutability explain a lot of thing about how Angular 2 detect changes of object, and how the tree of components in angular 2 is traversed to perform data binding...
In your sample, I think your component "my-app" can be considered to be "Immutable", so changing its "change detection strategy" to OnPush solve your problem.
You can write this :
#Component({
selector: 'my-app',
changeDetection: ChangeDetectionStrategy.OnPush,
template : `
<div>
<ul>
<li *ng-for="#item of m_items">
{{item.getTitle()}}
</li>
</ul>
</div>
`
})
And after adding the import to ChangeDetectionStrategy, databinding of "my-app" will not be computed after each browser event, but only when its input change, so never...