angular 2 Leaflet dynamic binding on popup - leaflet

I'm newbie on angular2. I've a map with some maker and I'm able to bind into popup content a static html code
L.marker([long, lat], {
icon: L.icon({ iconUrl: 'assets/marker-icon.png', shadowUrl: 'assets/marker-shadow.png' })
}).bindPopup(HTMLCONTENT).addTo(this.map);
I would insert into my HTMLCONTENT a dynamic content in order to use *ngFor, (click) or other angular facilities, so I've build a new angular component like in the following lines
markerlink.componentes.ts
import {Component, Input} from '#angular/core';
import 'rxjs/add/operator/map';
#Component({
selector: 'app-marker-link',
templateUrl: './markerlink.template.html'
})
export class MarkerLinkComponent {
#Input()
points: any[] = [];
}
markerlink.template.html
<div *ngFor="let single of points">
{{single.name}}
</div>
At compile time all works fine and a map with all markers is generated when I go to index but when I click on one of them I see an empty popup.
Maybe there is an incorrect configuration or there is another way to do this?

seems to be a typo of "HTMLCONTENT" in your code below
L.marker([long, lat], {
icon: L.icon({ iconUrl: 'assets/marker-icon.png', shadowUrl: 'assets/marker-shadow.png' })
}).bindPopup(HTMLCONTETN).addTo(this.map);

Related

Svelte / SvelteKit importing an npm library returns error when trying to work with Leaflet

I'm trying to learn Svelte / SvelteKit by porting over an existing Angular application. The app should show a Leaflet map with a heatmap layer as overlay. The map part works and is robust, even when I navigate or refresh Svelte handles it fine. The heatmap on the other hand only loads when the app initializes for the first time as you can see here:
However When I refresh I get this error and the whole Map.svelte component doesn't load at all anymore with the following error message in the console:
Uncaught (in promise) TypeError: Leaflet.heatLayer is not a function
I suspect it has to do with the way the lifecycle handles imports, because in my Angular app the imports don't have to be done in a life cycle method in order for them to work, whereas the only way to get Leaflet to even render in SvelteKit I have to do an async import.
Can anyone clarify what's going on with the Leaflet.heatlayer error and how I can fix it?
Map.svelte
<script lang="ts">
import type { HeatLayer, Map } from 'leaflet';
import { onMount } from 'svelte';
let Leaflet;
let map: Map;
let heatLayer: HeatLayer;
onMount(async () => {
Leaflet = await import('leaflet');
import('leaflet.heat');
const heatLatLngTuple = await fetchData(); // fetchData returns data from JSON file
const mapTiles = Leaflet.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap'
});
heatLayer = Leaflet.heatLayer(heatLatLngTuple, {. // THIS LINE IS CAUSING THE ERROR
radius: 20,
blur: 25,
minOpacity: 0,
maxZoom: 6,
max: 12
});
map = Leaflet.map('map', {
center: [51.505, -0.09],
zoom: 6,
layers: [mapTiles, heatLayer]
});
});
</script>
<div id="map" />
Things I've tried:
including 'leaflet-heat.js' from node_modules in a <script> tag in app.html
including 'leaflet-heat.js' from node_modules in a <script> tag in __layout.svelte
including 'leaflet-heat.js' from node_modules in a <script> tag in index.svelte
importing leaflet.heat at the top of Map.svelte with "import 'leaflet.heat'" <- THIS WORKED IN ANGULAR! but here it just results in this error
ReferenceError: window is not defined
putting a tick() before assigning heatLayer in Map.svelte
Resources:
My GitHub repo
Leaflet.heat
As this answer points out there is an additional way of importing that I didn't know about using
vite-plugin-iso-import
After getting that set up my component now works and after importing "leaflet.heat" with ?client added and moved to the top level of my imports. Here is the link to the FAQ with a detailed explanation.
After the changes my component now looks like this:
Map.svelte
<script lang="ts">
import type { HeatLayer, Map } from 'leaflet';
import { onMount } from 'svelte';
import Leaflet from 'leaflet?client'; // provides definition of 'L' needed by Leaflet
import 'leaflet.heat?client'; // Note the '?client' after the module name which makes sure 'leaflet.heat' always has access to the 'window' object
let map: Map;
let heatLayer: HeatLayer;
onMount(async () => {
const heatLatLngTuple = await fetchData();
const mapTiles = Leaflet.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap'
});
heatLayer = Leaflet.heatLayer(heatLatLngTuple, {. // THIS LINE IS CAUSING THE ERROR
radius: 20,
blur: 25,
minOpacity: 0,
maxZoom: 6,
max: 12
});
map = Leaflet.map('map', {
center: [51.505, -0.09],
zoom: 6,
layers: [mapTiles, heatLayer]
});
});
</script>
<div id="map" />
I had this same issue on refresh, you need import the module inside the on mount. It's not the same as your code. But you get that point.
onMount(async () => {
const leafletModule = await import('leaflet');
L = leafletModule.default;

Leaflet 1.7: L.MarkerClusterGroup is not a function

I'm trying to use MarkerClusterGroup on a leaflet map. I have the error L.MarkerClusterGroup is not a function. I've read the related threads, but they are only valid for versions below leaflet 1.7.
I'm using React with webpack.
import { Icon, Marker, Circle, LatLngBounds, Popup, DivIcon } from "leaflet";
import "leaflet.markercluster";
const divIcon = new DivIcon();
const markersCluster = L.MarkerClusterGroup({
chunkedLoading: true,
iconCreateFunction: function (cluster) {
return divIcon({
html: cluster.getChildCount(),
className: "mycluster",
iconSize: null,
});
},
});
I've also tried to import L globally:
import * as L from "leaflet";
import "leaflet.markercluster";
const divIcon = new L.DivIcon();
const markersCluster = L.MarkerClusterGroup({
chunkedLoading: true,
iconCreateFunction: function (cluster) {
return divIcon({
html: cluster.getChildCount(),
className: "mycluster",
iconSize: null,
});
},
});
How to fix this?
Depending on your build engine, the imported L namespace from "leaflet" may not be augmented with MarkerClusterGroup (from leaflet.markercluster plugin) unfortunately.
But you can resort to using window.L instead, which is always augmented by the plugin.
BTW, either use the class constructor form with new keyword: new window.L.MarkerClusterGroup(), or use the factory form with lowerCamelCase: L.markerClusterGroup()
import * as L from "leaflet";
import "leaflet.markercluster";
console.log(window.L === L); // false...
const divIcon = new L.DivIcon();
const markersCluster = new window.L.MarkerClusterGroup({ // Use window.L for plugins
chunkedLoading: true,
iconCreateFunction: function (cluster) {
return divIcon({
html: cluster.getChildCount(),
className: "mycluster",
iconSize: null,
});
},
});
Live demo: https://stackblitz.com/edit/js-ojki89?file=index.js
This error raised on my app after upgrade leaflet version to 1.9.1 from 1.8.0
Window.L.markerClusterGroup() instead of L.markerClusterGroup() works for me.

Angular 2 Error: Cannot find control with unspecified name attribute - Components inside components

I have to create a form , And I want to be more clear about components
The main idea of form is:
FormComponent
|>
FieldComponent
|> InputComponent of Form
So I Have PartnerFormComponent:
Html:
<form [formGroup]="partnerForm" (ngSubmit)="save()">
<!--<legal-information
[(partner)]="partner" [(partnerConfiguration)]="partnerConfiguration" ></legal-information>-->
<combo-billing-entity [(formGroup)]="partnerForm" [(partner)]="partner" [(partnerConfiguration)]="partnerConfiguration"></combo-billing-entity>
<div class="buttons_form">
<button class="save_button_form" type="submit" [disabled]="!partnerForm.valid">
Add
</button>
<a class="btn-floating btn-large waves-effect waves-light green"
routerLink="/partners">
<i class="material-icons">Cancel</i>
</a>
</div>
</form>
And ts:
#Component({
selector: 'partner-form',
templateUrl: './partner-form.component.html',
styleUrls: ['./partner-form.component.css'],
entryComponents:[LegalInformationComponent]
})
export class PartnerFormComponent implements OnInit {
private partnerForm: FormGroup;
title: string;
partner: Partner = new Partner();
partnerConfiguration: PartnerConfiguration = new PartnerConfiguration();
constructor(
private router: Router,
private route: ActivatedRoute,
private partnerService: PartnerService
) { }
ngOnInit() {
var id = this.route.params.subscribe(params => {
var id = params['id'];
if (!id)
return;
.....
});
}
then from component i have html Combo:
<div class="components_situation">
<div class="field_form_title">
{{title}} <span class="is_required_form" [hidden]="!isRequired">*</span>
</div>
<div [formGroup]="formGroup" >
<select id="billingEntity" [(ngModel)]="partnerConfiguration.fakeBillingEntity"
formControlName="billingEntity"
[class.invalid]="form.controls['billingEntity'].touched && !form.controls['billingEntity'].valid"
>
<option disabled hidden [value]="selectUndefinedOptionValue">-- select --</option>
<option *ngFor="let obj of billingEntities" [value]="obj.value" >{{obj.name}}</option>
</select>
</div>
<div class="some_explanation_form_field">{{someExplanation}}</div>
</div>
And TS:
import {Component, Input, OnInit} from "#angular/core";
import {CommonFieldFormComponent} from "../../common-field-form-component";
import {BillingService} from "../../../../../../services/billing/billing.service";
import {BillingEntitity} from "../../../../../../model/billing_entity";
import {FormBuilder, FormGroup, Validators} from "#angular/forms";
#Component({
selector: 'combo-billing-entity',
templateUrl: './combo-billing-entity.component.html'
})
export class ComboBillingEntityComponent extends CommonFieldFormComponent implements OnInit {
private selectUndefinedOptionValue:any;
billingEntities:BillingEntitity[] = [];
#Input()
private formGroup:FormGroup;
constructor(private billingService: BillingService, private formBuilder:FormBuilder)
{
super();
this.isRequired=true;
this.title="Billing Entity";
this.someExplanation="Identifies entity responsible for billing invoice";
this.formGroup = this.formBuilder.group({
billingEntity :['', Validators.required]
});
}
But after all I have this error:
ComboBillingEntityComponent.html:5 ERROR Error: Cannot find control with unspecified name attribute
at _throwError (forms.es5.js:1852)
at setUpControl (forms.es5.js:1760)
at FormGroupDirective.webpackJsonp.../../../forms/#angular/forms.es5.js.FormGroupDirective.addControl (forms.es5.js:4733)
at FormControlName.webpackJsonp.../../../forms/#angular/forms.es5.js.FormControlName._setUpControl (forms.es5.js:5321)
at FormControlName.webpackJsonp.../../../forms/#angular/forms.es5.js.FormControlName.ngOnChanges (forms.es5.js:5239)
at checkAndUpdateDirectiveInline (core.es5.js:10831)
at checkAndUpdateNodeInline (core.es5.js:12330)
at checkAndUpdateNode (core.es5.js:12269)
at debugCheckAndUpdateNode (core.es5.js:13130)
at debugCheckDirectivesFn (core.es5.js:13071)
Any Idea how to bind inputs to main form.. what I'm doing wrong ?
You have quite a few issues in your form. You are trying to use two-way-binding in your Input fields, for example [(formGroup)]="partnerForm" But then you are nowhere using #Output to actually trigger the two-way-binding, so you are not using it properly.
The form object is indeed an object, and objects are mutable in JS and passed by reference. Sometimes this is not desirable behavior, but in this scenario we want that, where we have nested components. So whatever you do to form fields in child component, parent will be aware, so you don't actually need the two-way-binding.
Secondly, please avoid using [(ngModel)] in reactive forms. I've noticed that weird behavior can occur, which is understandable, since we then have two bindings, to the ngModel variable and the other is the form control variable. Utilize the form controls instead, and remove all ngModel from your templates. You can set values to the form control, that will basically function as two-way-binding as you can access the form control value from TS as well any time you like. So [(ngModel)]
Build your whole form in parent and then pass the formgroup over to the child, or alernatively pass a nested formgroup to your child. So here in your parent you actually want to build the form:
this.partnerForm = this.fb.group({
billingEntity: ['hey I am initial value']
})
Above you cans set initial value to billingEntity, or if you need at some other point manually set a default value you can do that by: this.partnerForm.get('billingEntity').setValue(...)
We now pass this form to the child:
<combo-billing-entity [partnerForm]="partnerForm"></combo-billing-entity>
and in child we register it as an Input:
#Input() formGroup: FormGroup;
Then we can then just use it, e.g:
<div [formGroup]="partnerForm">
<input formControlName="billingEntity" />
</div>
I see that you are trying to use [(ngModel)] there, but as mentioned, you can drop that and use the form control instead. The value is stored nicely in formGroup.get('billingEntity').value and as earlier mentioned, if you need to set the value at some point you can do that. But all your form values are nicely stored in the form object, i.e partnerForm.value.
Here's a simple demo: http://plnkr.co/edit/AuidEMaaURsBPfDP8k0Q?p=preview
I suggest you read about nested forms, this one is pretty good to get started with: Nested Model-driven Forms
Your component will have to implement ControlValueAccessor and register it is a multi dependency. Pascal Precht has a very good blog post on custom form controls in Angular.
In short words, after you implement ControlValueAccessor by defining functions writeValue, registerOnChange and registerOnTouched, you'll have to provide the existing value so Angular can know that you're trying to use it a form control.
#Component({
// ...
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ComboBillingEntityComponent),
multi: true,
}
],
// ...
})
class ComboBillingEntityComponent implements ControlValueAccessor {
// ...
}
After this, you can use formControlName on <combo-billing-entity> tag in your templates:
<combo-billing-entity formControlName="billingInfo"></combo-billing-entity>

Angular TypeScript innerHTML binding for input components

let's say I retrieve a [string] of HTML codes from MongoDB such as
["<div class=list-group-item><span><label>Enter text</label> : <input type =text placeholder=value [(ngModel)]=text0 index=gen > </span> </div>",
"<div class=list-group-item><span><label>Enter text</label> : <input type =text placeholder=value [(ngModel)]=text1 index=gen > </span> </div>"]
and I process I sanitize it and want to make it into a form with full functionality!
my html code:
<p>
DATA:
{{this.id}}
</p>
<span *ngFor="let item of this.code;let x= index;"><div [innerHTML]="sanitizer.bypassSecurityTrustHtml(this.code[x])" ></div>
{{this.item}}<br>
WORK GOdDAMMIT!!: {{text0}}
</span>
I hard coded text0 to see if it decides to work!
Right now, Could you guys give me a basic example on how I could access the ngModel value so I can do it?
In AngularJS(v1.6) I managed to do it with an external module called bind-html-compile
Any small examples or such would be very beneficial!
If you want to have a look at my component.ts, here:
#Component({
selector: 'app-form-show',
templateUrl: './form-show.component.html',
styleUrls: ['./form-show.component.css'],
providers: [FormShowService]
})
export class FormShowComponent implements OnInit {
private id:string;
private code:string;
//private htmlCode :HTML
constructor(
private http: Http,
private router: Router,
private route : ActivatedRoute,
private sanitizer: DomSanitizer
) {
}
ngOnInit(){
//console.log(this.Code)
console.log(this.route.params['value'].id);
this.id=this.route.params['value'].id
this.getID();
}
getID(){
let jsonData : JSON;
jsonData=JSON.parse('{"data" :"'+ this.id +'"}');
return this.http.post('/api/getInfo', jsonData).subscribe(
res => {res.json();
console.log(res.json());
let resProc = res.json();
console.log(resProc);
this.code=resProc[0].script;
console.log(this.code);
});
}
}
Thanks!
I have a much simpler solution for you and any of you who might run into a similar situation.I tried Viewcontainerreff but, to no avail.. After searching through npm.js, I believe that all you need is this package!
The Fantabulous ng-dynamic
and it's usage is also pretty simple:
<div *dynamicComponent="your_html_string">loadin</div>
It works with angular4 directives and is all you need for such purposes!
Good Luck
~Ronny McNamara

react-leaflet popup not working, cursor doesn't change on mouseover

I am using react-leaflet, and can get a market to appear. The is just not working.
I've simply copied the example code. Could there be conflicts with other packages? Or do I need particular versions of leaflet, react and react-dom for this to work?
My cursor doesn't change on mouseover the map.
I have made sure I have the correct css so the map and marker render correctly.
Any help would be greatly appreciated, I am fairly new at this, so it is likely a silly error.
import React from 'react';
import ReactDOM from 'react-dom';
import { Map, TileLayer, Marker, Popup } from 'react-leaflet';
class SimpleExample extends React.Component {
constructor() {
super();
this.state = {
lat: 51.505,
lng: -0.09,
zoom: 13,
};
}
render() {
const position = [this.state.lat, this.state.lng]
return (
<Map center={position} zoom={this.state.zoom}>
<TileLayer
attribution='&copy OpenStreetMap contributors'
url='http://{s}.tile.osm.org/{z}/{x}/{y}.png'
/>
<Marker position={position}>
<Popup>
<span>A CSS3 popup. <br /> Easily customizable.</span>
</Popup>
</Marker>
</Map>
);
}
}
ReactDOM.render(<SimpleExample />, document.getElementById('root'));
Check your index file and see if the css file/js file for leaflet are for version 0.7.7 or 1.0. If it is on 1.0, replace them with:
https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.css
https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/leaflet.js