Angular ve Leaflet ile Harita Oluşturma, Bölüm 3: Popup Servisi
Giriş: Haritalarınızı Etkileşimli Hale Getirin
Angular ve Leaflet kullanarak etkileşimli haritalar oluşturma serimizin üçüncü bölümüne hoş geldiniz. Önceki bölümlerde, Angular uygulamalarımıza temel bir Leaflet haritasını nasıl entegre edeceğimizi, harita katmanlarını (tile layers) nasıl yöneteceğimizi ve işaretçileri (markers) nasıl ekleyeceğimizi öğrendik. Bu temel bilgilerin üzerine inşa ederek, bu bölümde haritalarımızı kullanıcılarla daha etkileşimli hale getiren önemli bir bileşen olan “popup”ları ele alacağız.
Popuplar, harita üzerindeki belirli bir noktaya veya özelliğe tıklandığında ortaya çıkan küçük bilgi pencereleridir. Bir işaretçinin, bir poligonun veya herhangi bir coğrafi özelliğin detaylarını, açıklamalarını veya ilgili diğer verileri göstermek için kullanılırlar. Ancak, Leaflet’in varsayılan popupları genellikle basit metin veya statik HTML içeriği kabul eder. Angular gibi modern bir çerçeveyle çalışırken, bu popupların içine dinamik Angular bileşenleri veya şablonları yerleştirmek isteyebiliriz. Bu sayede, popuplarımız sadece bilgi göstermekle kalmayacak, aynı zamanda Angular’ın tüm gücüyle etkileşimli formlar, düğmeler veya karmaşık veri görselleştirmeleri içerebilecektir.
İşte bu noktada “Popup Servisi” devreye giriyor. Bu makalede, Leaflet popuplarını Angular bileşenleriyle entegre etmek için güçlü ve yeniden kullanılabilir bir servis yapısı nasıl oluşturulacağını adım adım inceleyeceğiz. Bu servis, Angular bileşenlerinin veya şablonlarının yaşam döngüsünü (oluşturma, veri aktarımı, yok etme) Leaflet popup’ları içinde yönetmemizi sağlayacak, böylece temiz ve sürdürülebilir bir kod tabanı elde edeceğiz.
Popup Nedir ve Neden Önemlidir?
Popup’lar (açılır pencereler), harita üzerindeki belirli bir coğrafi özelliğe (bir işaretçi, bir çizgi, bir alan vb.) bağlı olarak bilgi gösteren küçük, geçici kullanıcı arayüzü öğeleridir. Genellikle kullanıcı bir harita özelliğine tıkladığında veya üzerine geldiğinde görünürler ve ilgili özelliğin detaylarını sunarlar.
Neden Önemlidirler?
1. Bağlamsal Bilgi: Kullanıcıya, baktığı belirli bir nokta veya alan hakkında anında, bağlamsal bilgi sağlarlar. Örneğin, bir şehir işaretçisine tıklandığında şehrin nüfusunu, önemli yerlerini veya kısa bir tarihini gösterebilir.
2. Etkileşim: Popuplar sadece bilgi göstermekle kalmaz, aynı zamanda etkileşimli öğeler (düğmeler, bağlantılar, formlar) içerebilir. Bu sayede kullanıcılar popup üzerinden doğrudan eylemler gerçekleştirebilirler.
3. Kullanıcı Deneyimi: Harita üzerinde çok fazla bilgi göstermek yerine, bilgiyi ihtiyaç duyulduğunda sunarak haritanın daha temiz ve anlaşılır kalmasını sağlarlar. Bu, daha iyi bir kullanıcı deneyimi sunar.
4. Veri Zenginleştirme: Harita üzerindeki basit bir işaretçinin ardında yatan zengin verileri kullanıcıya sunmanın etkili bir yoludur.
Yaygın Kullanım Alanları:
* İşaretçi Detayları: Bir restoranın, mağazanın veya tarihi bir yerin adını, adresini, çalışma saatlerini ve derecelendirmesini gösterme.
* Coğrafi Bölge Bilgisi: Bir ülkeye veya eyalete tıklandığında nüfusu, başkenti veya ekonomik verileri gösterme.
* Rota Bilgisi: Bir rotanın belirli bir noktası hakkında tahmini varış süresi veya trafik durumu gibi bilgiler sunma.
* Sensör Verileri: Hava durumu istasyonları veya IoT cihazlarından gelen anlık verileri görselleştirme.
Leaflet’te Temel Popup Kullanımı
Leaflet, popupları yönetmek için basit ve etkili bir API sunar. Temel olarak, bir popup’ı bir işaretçiye veya başka bir katmana bağlayabilir ve içine metin veya HTML içeriği yerleştirebilirsiniz.
İşaretçiye Popup Bağlama
En yaygın kullanım senaryosu, bir işaretçiye popup bağlamaktır.
import * as L from 'leaflet';
// Harita oluşturma (önceki bölümlerden)
const map = L.map('map').setView([39.9334, 32.8597], 10);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
// Bir işaretçi oluşturma
const marker = L.marker([39.9334, 32.8597]).addTo(map);
// İşaretçiye basit bir popup bağlama
marker.bindPopup("Merhaba!
Bu bir Leaflet popup'ıdır.").openPopup();
// openPopup() çağrısı, harita yüklendiğinde popup'ın otomatik olarak açılmasını sağlar.
// Genellikle bu, kullanıcının işaretçiye tıklamasıyla tetiklenir, bu durumda openPopup() çağrısına gerek kalmaz.
bindPopup() metodu, bir popup’ı bir katmana (marker, circle, polygon vb.) bağlar. Kullanıcı katmana tıkladığında popup otomatik olarak açılır. openPopup() metodu ise popup’ı programatik olarak açar.
Popup Seçenekleri
bindPopup() ve L.popup() metodları, popup’ın davranışını ve görünümünü özelleştirmek için bir dizi seçenek alır:
const customPopup = L.popup({
maxWidth: 300, // Popup'ın maksimum genişliği
minWidth: 100, // Popup'ın minimum genişliği
autoClose: true, // Haritanın başka bir yerine tıklandığında popup'ın otomatik kapanması
closeButton: true, // Popup'ta bir kapatma düğmesi gösterilmesi
closeOnClick: true, // Haritanın başka bir yerine tıklandığında popup'ın kapanması (autoClose ile benzer)
className: 'my-custom-popup' // Popup'a özel bir CSS sınıfı ekleme
})
.setLatLng([39.95, 32.90]) // Popup'ın açılacağı koordinatlar
.setContent("Özel Popup
Çeşitli seçeneklerle.");
// Popup'ı haritaya ekleme (bir işaretçiye bağlı değilse)
// customPopup.openOn(map); // Bu, popup'ı doğrudan harita üzerinde açar.
// Bir işaretçiye bağlamak için:
const markerWithOptions = L.marker([39.95, 32.90]).addTo(map);
markerWithOptions.bindPopup(customPopup); // Önceden oluşturulmuş bir popup nesnesini bağlama
setLatLng() ve setContent() metodları, bir popup nesnesi oluşturulduktan sonra içeriğini ve konumunu dinamik olarak ayarlamak için kullanılır. openOn(map) ise popup’ı doğrudan harita üzerinde açar ve kapatma olaylarını harita ile ilişkilendirir.
Diğer Katmanlarda Popup Kullanımı
Popuplar sadece işaretçilerle sınırlı değildir. Poligonlar, çoklu çizgiler (polylines) ve daireler (circles) gibi diğer Leaflet katmanlarına da bağlanabilirler.
// Bir daireye popup bağlama
const circle = L.circle([39.92, 32.80], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 500
}).addTo(map);
circle.bindPopup("Bu bir daire! Yarıçapı 500 metre.");
// Bir poligona popup bağlama
const polygon = L.polygon([
[39.94, 32.82],
[39.96, 32.85],
[39.94, 32.88]
], {
color: 'blue',
fillColor: '#00f',
fillOpacity: 0.3
}).addTo(map);
polygon.bindPopup("Bu bir poligon! Ankara'nın bir bölgesi.");
GeoJSON Katmanları ve Popuplar:
GeoJSON katmanları genellikle birden fazla özelliği içerir. Her bir özelliğe ayrı ayrı popup bağlamak yerine, onEachFeature seçeneğini kullanarak her bir GeoJSON özelliğine dinamik olarak popup bağlayabiliriz:
const geojsonFeature = {
"type": "Feature",
"properties": {
"name": "Kuğulu Park",
"amenity": "Park",
"popupContent": "Ankara'nın simgelerinden Kuğulu Park."
},
"geometry": {
"type": "Point",
"coordinates": [32.8656, 39.9079] // Leaflet'te LatLng bekler, GeoJSON'da LngLat
}
};
L.geoJSON(geojsonFeature, {
onEachFeature: function (feature, layer) {
if (feature.properties && feature.properties.popupContent) {
layer.bindPopup(feature.properties.popupContent);
}
}
}).addTo(map);
Bu temel kullanımlar, Leaflet’in popuplarla ne kadar esnek olduğunu gösterir. Ancak, Angular bileşenlerinin dinamik gücünü bu popuplara taşımak için daha gelişmiş bir yaklaşıma ihtiyacımız var.
Angular Entegrasyon Zorlukları ve Neden Bir Servis İhtiyacı
Leaflet’in bindPopup() veya setContent() metodları, genellikle düz metin (string) veya ham HTML (string) bekler. Angular uygulamasında, kullanıcı arayüzümüzü bileşenler (components) ve şablonlar (templates) aracılığıyla oluştururuz. Bu bileşenler, kendi yaşam döngüleri, veri bağlama mekanizmaları, olay dinleyicileri ve bağımlılık enjeksiyonu gibi Angular özelliklerine sahiptir.
Leaflet popup’ına doğrudan bir Angular bileşeninin HTML’ini string olarak vermek, bileşenin Angular tarafından yönetilen yaşam döngüsünü, veri bağlamasını veya olay dinleyicilerini kaybetmesine neden olur. Yani, popup’ın içindeki bir düğmeye tıklamak veya bir input alanına veri girmek gibi Angular’ın yönettiği etkileşimler çalışmayacaktır.
Karşılaşılan Zorluklar:
1. Angular Yaşam Döngüsü Yönetimi: Bir Angular bileşeni, ngOnInit, ngOnDestroy gibi yaşam döngüsü hook’larına sahiptir. Leaflet popup’ına yerleştirilen bir bileşenin bu hook’ları doğru şekilde tetiklenmezse, bellek sızıntıları veya beklenmedik davranışlar ortaya çıkabilir.
2. Veri Bağlama ve Olay Yönetimi: [(ngModel)], (click), @Input, @Output gibi Angular’ın temel özellikleri, bileşen Leaflet tarafından yönetildiğinde çalışmaz.
3. Bağımlılık Enjeksiyonu: Popup içindeki bir bileşenin başka servisleri kullanması gerektiğinde, Angular’ın bağımlılık enjeksiyon mekanizması doğal olarak devreye giremez.
4. Yeniden Kullanılabilirlik: Her popup için manuel olarak DOM manipülasyonu yapmak veya bileşenleri el ile oluşturmak, kod tekrarına ve bakım zorluğuna yol açar.
Bu sorunları çözmek ve Angular’ın tüm gücünü Leaflet popuplarına taşımak için bir “Popup Servisi”ne ihtiyacımız var. Bu servis, Angular bileşenlerini veya şablonlarını dinamik olarak oluşturacak, onları Leaflet popup’ının içine yerleştirecek ve popup kapandığında bileşenin doğru şekilde temizlenmesini sağlayacaktır.
Servisin Temel Görevleri:
* Dinamik Bileşen Oluşturma: Verilen bir Angular bileşen tipinden (örneğin MarkerPopupComponent) dinamik olarak bir örnek oluşturmak.
* DOM Ekleme: Oluşturulan bileşenin DOM elementini Leaflet popup’ının içeriği olarak sağlamak.
* Veri Aktarımı: Popup bileşenine dışarıdan veri (@Input ile) aktarmak.
* Yaşam Döngüsü Yönetimi: Bileşeni Angular uygulama bağlamına ekleyerek yaşam döngüsünü başlatmak ve popup kapandığında bileşeni doğru şekilde yok etmek.
* Şablon Entegrasyonu: Sadece bileşenler değil, aynı zamanda ng-template ile tanımlanmış Angular şablonlarını da popuplara yerleştirebilmek.
Bu servisi oluşturarak, harita bileşenimiz içinde temiz ve Angular’a özgü bir şekilde popuplarımızı yönetebileceğiz.
Popup Servisini Tasarlamak
Angular’da dinamik olarak bileşen oluşturmak ve bunları DOM’a eklemek için bazı Angular çekirdek API’lerini kullanmamız gerekir. Bu API’ler genellikle bir servis içinde sarmalanarak yeniden kullanılabilir hale getirilir.
Kullanılacak Angular API’leri:
1. ComponentFactoryResolver: Bir Angular bileşeninin Type‘ından (örneğin MarkerPopupComponent) bir ComponentFactory oluşturmak için kullanılır. Bu fabrika, daha sonra bileşenin örneklerini oluşturabilir.
2. ApplicationRef: Bir Angular uygulamasının ana örneğidir. Dinamik olarak oluşturulan bileşenlerin host view’lerini Angular’ın değişiklik algılama (change detection) döngüsüne ve yaşam döngüsü yönetimine dahil etmek için kullanılır. attachView() ve detachView() metodları bu amaçla kullanılır.
3. Injector: Bağımlılık enjeksiyonu için kullanılır. Dinamik olarak oluşturulan bileşenlerin kendi bağımlılıklarını çözebilmesi için bir injector sağlamak önemlidir.
4. EmbeddedViewRef / TemplateRef: Eğer bir bileşen yerine bir ng-template içeriğini popup’a yerleştirmek istersek, TemplateRef‘i kullanarak bir EmbeddedViewRef oluşturabiliriz.
Servis Yapısı:
MapPopupService adında bir servis oluşturacağız. Bu servis, iki ana metot içerecek:
* openComponentPopup(): Bir Angular bileşenini Leaflet popup’ı olarak açmak için.
* openTemplatePopup(): Bir Angular şablonunu (ng-template) Leaflet popup’ı olarak açmak için.
Her iki metot da şu parametreleri alacaktır:
* map: L.Map: Popup’ın hangi harita üzerinde açılacağını belirten Leaflet harita nesnesi.
* latlng: L.LatLngExpression: Popup’ın açılacağı coğrafi koordinatlar.
* componentType: Type veya templateRef: TemplateRef: Popup’ın içeriği olacak Angular bileşeni veya şablonu.
* data?: any veya context?: any: Popup bileşenine (@Input ile) veya şablonuna (let- sözdizimi ile) aktarılacak veriler.
* options?: L.PopupOptions: Leaflet popup’ının özelleştirme seçenekleri (maxWidth, className vb.).
Servis ayrıca, popup kapandığında dinamik olarak oluşturulan Angular bileşenlerinin veya şablon görünümlerinin Angular’ın ApplicationRef‘inden ayrılmasını ve yok edilmesini sağlayarak bellek sızıntılarını önleyecektir. Bu, L.popup nesnesinin remove olayını dinleyerek gerçekleştirilecektir.
Şimdi bu tasarımı adım adım kodlayarak hayata geçirelim.
Adım Adım Uygulama
Bu bölümde, Popup Servisimizi oluşturacağız, bir örnek popup bileşeni tanımlayacağız ve ardından bu servisi harita bileşenimizde nasıl kullanacağımızı göstereceğiz.
1. Popup Servisini Oluşturma (map-popup.service.ts)
// src/app/services/map-popup.service.ts
import {
Injectable,
ComponentFactoryResolver,
ApplicationRef,
Injector,
Type,
EmbeddedViewRef,
TemplateRef,
ComponentRef
} from '@angular/core';
import * as L from 'leaflet';
@Injectable({
providedIn: 'root'
})
export class MapPopupService {
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private appRef: ApplicationRef,
private injector: Injector
) {}
/
* Bir Angular bileşenini Leaflet popup'ı olarak açar.
* @param map Popup'ın açılacağı Leaflet harita nesnesi.
* @param latlng Popup'ın açılacağı koordinatlar.
* @param componentType Popup içeriği olacak Angular bileşeninin tipi.
* @param data Bileşene aktarılacak @Input verileri.
* @param options Leaflet popup seçenekleri.
* @returns Açılan Leaflet Popup nesnesi.
*/
openComponentPopup(
map: L.Map,
latlng: L.LatLngExpression,
componentType: Type,
data?: any,
options?: L.PopupOptions
): L.Popup {
// 1. Bileşen Fabrikası Oluşturma
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType);
// 2. Bileşen Örneği Oluşturma
// Injector'ı kullanarak bileşenin bağımlılıklarını çözmesini sağlıyoruz.
const componentRef: ComponentRef = componentFactory.create(this.injector);
// 3. Bileşene veri aktarımı (@Input)
if (data) {
Object.assign(componentRef.instance, data);
// Değişiklik algılamayı manuel olarak tetikleme
componentRef.changeDetectorRef.detectChanges();
}
// 4. Bileşenin görünümünü Angular uygulama ağacına ekleme
// Bu, bileşenin yaşam döngüsü hook'larının (ngOnInit vb.) tetiklenmesini ve
// değişiklik algılamanın çalışmasını sağlar.
this.appRef.attachView(componentRef.hostView);
// 5. Bileşenin DOM elementini alma
// Host view'den rootNodes'u alarak bileşenin ana DOM elementine erişiyoruz.
const popupContent = (componentRef.hostView as EmbeddedViewRef).rootNodes[0] as HTMLElement;
// 6. Leaflet Popup oluşturma ve içeriği ayarlama
const popup = L.popup(options)
.setLatLng(latlng)
.setContent(popupContent)
.openOn(map);
// 7. Popup kapandığında bileşeni temizleme
// Popup'ın 'remove' olayını dinleyerek, Angular bileşeninin
// uygulama ağacından ayrılmasını ve yok edilmesini sağlıyoruz.
// Bu, bellek sızıntılarını önler.
popup.on('remove', () => {
this.appRef.detachView(componentRef.hostView);
componentRef.destroy();
});
return popup;
}
/
* Bir Angular şablonunu (ng-template) Leaflet popup'ı olarak açar.
* @param map Popup'ın açılacağı Leaflet harita nesnesi.
* @param latlng Popup'ın açılacağı koordinatlar.
* @param templateRef Popup içeriği olacak Angular şablonunun referansı.
* @param context Şablona aktarılacak bağlam verileri (let- ile erişilebilir).
* @param options Leaflet popup seçenekleri.
* @returns Açılan Leaflet Popup nesnesi.
*/
openTemplatePopup(
map: L.Map,
latlng: L.LatLngExpression,
templateRef: TemplateRef,
context?: any,
options?: L.PopupOptions
): L.Popup {
// 1. Şablon görünümünü oluşturma
// Context, şablondaki "let-" değişkenlerine aktarılacak veridir.
const viewRef: EmbeddedViewRef = templateRef.createEmbeddedView(context);
// 2. Görünümü Angular uygulama ağacına ekleme
this.appRef.attachView(viewRef);
// 3. Şablonun DOM elementini alma
const popupContent = (viewRef.rootNodes[0] as HTMLElement);
// 4. Leaflet Popup oluşturma ve içeriği ayarlama
const popup = L.popup(options)
.setLatLng(latlng)
.setContent(popupContent)
.openOn(map);
// 5. Popup kapandığında görünümü temizleme
popup.on('remove', () => {
this.appRef.detachView(viewRef);
viewRef.destroy();
});
return popup;
}
}
Önemli Not: Angular 9 ve üzeri versiyonlarda, Ivy derleyicisi sayesinde entryComponents dizisine bileşenleri eklemeye genellikle gerek kalmaz. Ancak eski versiyonlar veya bazı özel durumlarda, dinamik olarak oluşturulan bileşenlerin (bu örnekte MarkerPopupComponent) Angular modülünün @NgModule dekoratöründeki entryComponents dizisine eklenmesi gerekebilir.
2. Örnek Bir Popup Bileşeni Oluşturma (marker-popup.component.ts)
Şimdi, popup servisi aracılığıyla Leaflet popup’ının içinde görüntülenecek basit bir Angular bileşeni oluşturalım.
// src/app/components/marker-popup/marker-popup.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-marker-popup',
template:
{{ title }}
{{ description }}
{{ additionalInfo }}
,
styles: [
.custom-popup {
padding: 10px;
font-family: Arial, sans-serif;
}
h3 {
color: #3f51b5; / Angular Material primary color example /
margin-top: 0;
margin-bottom: 8px;
}
p {
color: #555;
font-size: 14px;
margin-bottom: 12px;
}
button {
background-color: #f44336; / Angular Material warn color example /
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #d32f2f;
}
]
})
export class MarkerPopupComponent {
@Input() title: string = 'Varsayılan Başlık';
@Input() description: string = 'Varsayılan Açıklama.';
@Input() additionalInfo: string | null = null; // Opsiyonel ek bilgi
@Output() detailsClicked = new EventEmitter(); // Olay yaymak için
constructor() {
console.log('MarkerPopupComponent constructor called');
}
ngOnInit() {
console.log('MarkerPopupComponent ngOnInit called', this.title);
}
ngOnDestroy() {
console.log('MarkerPopupComponent ngOnDestroy called', this.title);
}
onDetailsClick(): void {
alert(Detaylar için tıklandı: ${this.title});
this.detailsClicked.emit(this.title); // Bir olay yayma örneği
}
}
Bu bileşen, @Input dekoratörleri ile dışarıdan title, description ve additionalInfo alabilir. Ayrıca, onDetailsClick() metodu ile bir düğme tıklamasını işleyebilir ve hatta bir @Output EventEmitter kullanarak üst bileşene olay yayabilir. ngOnInit ve ngOnDestroy hook’ları, servisimizin yaşam döngüsü yönetimini doğru yaptığını doğrulamak için konsola mesaj yazacaktır.
3. Modül Yapılandırması (app.module.ts)
MarkerPopupComponent‘in uygulamanız tarafından tanınması için app.module.ts içinde declarations dizisine eklenmesi gerekir.
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { MapComponent } from './components/map/map.component'; // Harita bileşeniniz
import { MarkerPopupComponent } from './components/marker-popup/marker-popup.component'; // Yeni popup bileşeniniz
@NgModule({
declarations: [
AppComponent,
MapComponent,
MarkerPopupComponent // Popup bileşenini burada tanımlıyoruz
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
4. Harita Bileşeninde Servisi Kullanma (map.component.ts)
Şimdi MapComponent içinde MapPopupService‘i enjekte edip hem bileşen tabanlı hem de şablon tabanlı popupları nasıl kullanacağımızı gösterelim.
// src/app/components/map/map.component.ts
import { Component, OnInit, AfterViewInit, ViewChild, ElementRef, TemplateRef } from '@angular/core';
import * as L from 'leaflet';
import { MapPopupService } from '../../services/map-popup.service'; // Popup servisimiz
import { MarkerPopupComponent } from '../marker-popup/marker-popup.component'; // Popup bileşenimiz
@Component({
selector: 'app-map',
template:
Template Popup: {{ title }}
{{ description }}
,
styles: [
/ Leaflet popup'larının genel stilini özelleştirebiliriz /
.leaflet-popup-content-wrapper {
border-radius: 8px;
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
}
.leaflet-popup-tip {
background: #fff;
}
.custom-template-popup .leaflet-popup-content-wrapper {
background-color: #e0f2f7; / Template popup'ına özel arka plan /
}
.custom-template-popup .leaflet-popup-tip {
background: #e0f2f7;
}
]
})
export class MapComponent implements OnInit, AfterViewInit {
private map!: L.Map;
@ViewChild('mapContainer', { static: true }) mapContainer!: ElementRef;
@ViewChild('templatePopupContent') templatePopupContent!: TemplateRef;
constructor(private mapPopupService: MapPopupService) {}
ngOnInit(): void {}
ngAfterViewInit(): void {
this.initMap();
this.addMarkersWithPopups();
}
private initMap(): void {
this.map = L.map(this.mapContainer.nativeElement).setView([39.9334, 32.8597], 10); // Ankara merkez
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(this.map);
}
private addMarkersWithPopups(): void {
// 1. Angular Bileşeni ile Popup (Marker 1)
const marker1 = L.marker([39.9334, 32.8597], {
icon: L.icon({
iconUrl: 'assets/marker-icon.png', // Kendi marker ikonunuzu kullanabilirsiniz
shadowUrl: 'assets/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
})
}).addTo(this.map);
marker1.on('click', (e) => {
this.mapPopupService.openComponentPopup(
this.map,
e.latlng,
MarkerPopupComponent,
{ title: 'Ankara Kalesi', description: 'Ankara\'nın tarihi simgesi ve panoramik şehir manzarası sunar.', additionalInfo: 'Giriş ücreti alınabilir.' },
{ closeButton: true, maxWidth: 350, className: 'component-popup-style' }
);
});
// 2. Angular Şablonu ile Popup (Marker 2)
const marker2 = L.marker([39.91, 32.78], {
icon: L.icon({
iconUrl: 'assets/marker-icon-2x.png', // Farklı bir ikon
shadowUrl: 'assets/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
})
}).addTo(this.map);
marker2.on('click', (e) => {
if (this.templatePopupContent) {
this.mapPopupService.openTemplatePopup(
this.map,
e.latlng,
this.templatePopupContent,
{ title: 'Atakule', description: 'Ankara\'nın modern sembolü, alışveriş ve seyir terası.' },
{ closeButton: false, autoClose: true, className: 'custom-template-popup', maxWidth: 300 }
);
}
});
// 3. Basit Leaflet Popup (Karşılaştırma için Marker 3)
const marker3 = L.marker([39.95, 32.95]).addTo(this.map);
marker3.bindPopup('Merhaba!
Bu basit bir Leaflet popup.').openPopup();
}
// Şablon içinden çağrılacak bir metot
alertFromTemplate(title: string): void {
alert(Template'ten gelen bilgi: ${title});
}
}
assets Klasörü Notu: Yukarıdaki kodda assets/marker-icon.png, assets/marker-shadow.png gibi ikon yolları kullanılmıştır. Bu dosyaların src/assets klasörünüzde bulunduğundan emin olun. Leaflet’in varsayılan ikonları bazen CDN’den yüklenirken sorun çıkarabilir, bu nedenle yerel kopyalarını kullanmak iyi bir uygulamadır.
Bu kurulum ile artık harita üzerindeki işaretçilere tıklandığında, Angular’ın tam gücüne sahip etkileşimli popuplar açılacaktır. MarkerPopupComponent‘in ngOnInit ve ngOnDestroy konsol çıktılarını inceleyerek, servisimizin bileşen yaşam döngüsünü doğru yönettiğini görebilirsiniz. Popup açıldığında ngOnInit tetiklenecek, kapandığında ise ngOnDestroy tetiklenecektir.
Gelişmiş Konular ve Dikkate Alınması Gerekenler
Popup servisimiz, Angular bileşenlerini Leaflet popuplarına entegre etmek için sağlam bir temel sunar. Ancak, daha karmaşık senaryolar veya optimizasyonlar için bazı gelişmiş konuları göz önünde bulundurmak faydalı olacaktır:
1. Dinamik Veri Aktarımı ve Geri Bildirim:
* Çoklu @Input: Servisimizdeki data nesnesini kullanarak, popup bileşenine birden fazla @Input özelliği aktarabilirsiniz (Object.assign(componentRef.instance, data)).
* @Output Olayları: Popup bileşeninizdeki @Output EventEmitter’larını dinlemek için, componentRef.instance.detailsClicked.subscribe(...) gibi bir yapı kullanabilirsiniz. Bu sayede popup içindeki bir eylem (örneğin bir düğme tıklaması) harita bileşenine geri bildirim sağlayabilir.
* Servis Paylaşımı: Popup bileşeniniz, Angular’ın bağımlılık enjeksiyonu sayesinde diğer servisleri (örneğin bir veri servisi) kullanabilir. Bu, popup içinde karmaşık iş mantığı yürütmenizi sağlar.
2. Performans ve Bellek Yönetimi:
* Her popup açıldığında yeni bir Angular bileşeni oluşturulur ve kapatıldığında yok edilir. Çok sayıda dinamik popup’ı aynı anda açıp kapatmak, özellikle karmaşık bileşenler kullanılıyorsa performans üzerinde etkili olabilir.
* appRef.attachView() ve appRef.detachView() metodları, Angular’ın değişiklik algılama mekanizmasının doğru çalışmasını sağlar ancak dikkatli kullanılmalıdır. Servisimizdeki popup.on('remove', ...) ile temizleme adımı bu bellek sızıntılarını önlemek için kritik öneme sahiptir.
* Eğer aynı anda binlerce işaretçiniz varsa ve her biri için dinamik popup gerekiyorsa, performans optimizasyonu için sanallaştırma (virtualization) veya kümeleme (clustering) gibi teknikleri düşünmeniz gerekebilir.
3. Styling ve Temalandırma:
* Leaflet popuplarının varsayılan stillerini geçersiz kılmak için global CSS kuralları (örneğin leaflet-popup-content-wrapper, leaflet-popup-tip) veya popup seçeneklerinde className kullanarak özel sınıflar tanımlayabilirsiniz.
* Angular bileşenlerinizin kendi kapsüllenmiş stilleri (styles veya styleUrls ile) Leaflet popup’ı içinde de uygulanacaktır.
* Temalandırma (örneğin Angular Material veya Bootstrap ile), popup içeriğinin uygulamanızın genel görünümüne uyum sağlamasına yardımcı olur.
4. Alternatif Yaklaşımlar:
* ngx-leaflet veya Benzeri Kütüphaneler: Eğer uygulamanız ngx-leaflet gibi Angular için tasarlanmış Leaflet sarmalayıcı kütüphaneler kullanıyorsa, bu kütüphanelerin genellikle popupları Angular bileşenleriyle entegre etmek için kendi yerleşik mekanizmaları bulunur. Bu servis, sıfırdan Leaflet entegrasyonu yapanlar için veya bu kütüphanelerin sunduğundan daha fazla kontrol isteyenler için idealdir.
* Basit HTML Stringleri: Eğer popup içeriğinizde Angular’a özgü bir etkileşim veya veri bağlama ihtiyacınız yoksa, hala Leaflet’in bindPopup('
') metodunu kullanmak en basit ve en performanslı yoldur. Servisimiz, sadece Angular’ın dinamik özelliklerinden yararlanmak istediğinizde devreye girmelidir.
5. Hata Yönetimi ve Güvenlik:
* Dinamik bileşen oluşturma sırasında oluşabilecek hataları (örneğin, yanlış bileşen tipi verilmesi) ele almak için uygun try-catch blokları veya hata raporlama mekanizmaları ekleyebilirsiniz.
* Kullanıcıdan alınan verileri popup içeriğinde gösterirken XSS (Cross-Site Scripting) saldırılarına karşı dikkatli olun. Angular, varsayılan olarak XSS koruması sağlar, ancak manuel DOM manipülasyonu yaparken bu korumaları aşmamaya özen gösterin.
Bu gelişmiş konuları dikkate alarak, uygulamanızın ihtiyaçlarına göre popup servisinizi daha da güçlendirebilir ve optimize edebilirsiniz.
Sonuç
Bu makalede, Angular ve Leaflet entegrasyon serimizin üçüncü bölümü olarak, haritalarımıza dinamik ve etkileşimli popuplar eklemeyi öğrendik. Leaflet’in temel popup özelliklerinin ötesine geçerek, Angular bileşenlerini ve şablonlarını Leaflet popuplarının içine yerleştirmemizi sağlayan güçlü bir “Popup Servisi” tasarladık ve uyguladık.
Bu servis sayesinde:
* Angular bileşenlerinin tüm yaşam döngüsü (oluşturma, veri aktarımı, yok etme) Leaflet popupları içinde doğru bir şekilde yönetilir.
* @Input ile kolayca veri aktarımı yapabilir ve @Output ile popup içindeki olayları ana bileşenimize geri bildirebiliriz.
* ng-template kullanarak daha esnek ve hafif popup içerikleri oluşturabiliriz.
* Kod tekrarını azaltır, kodumuzu daha modüler ve sürdürülebilir hale getiririz.
Artık haritalarınız sadece statik bilgi göstermekle kalmayacak, aynı zamanda kullanıcılarınızla zengin ve anlamlı etkileşimler kurabilecek. Bu, modern web harita uygulamaları için vazgeçilmez bir özelliktir.
Serimizin bir sonraki bölümünde, haritalarımıza daha karmaşık çizim araçları (drawing tools) ekleyerek kullanıcıların kendi coğrafi verilerini oluşturmasına veya düzenlemesine olanak tanıyacağız. Harita geliştirme yolculuğunuzda başarılar dileriz!