Angular/Leaflet/OpenStreetMap - 单击弹出窗口的 pin 会引发有关已定义服务变量的错误,不会显示绑定的弹出窗口

Angular/Leaflet/OpenStreetMap - clicking on pin for pop-up throws error about an already defined service variable, doesn't display binded pop-up

当我点击任何图钉时,浏览器中出现错误。没有弹窗显示,没有任何反应,只是控制台报错。

您将在下面找到显示地图和图钉的组件,它应该会显示弹出窗口。服务在构造函数中注入。

使用图钉填充地图时成功使用了相同的服务变量。

import { Component, OnInit } from '@angular/core';
import * as L from 'leaflet';
import { PowerPlantService } from 'src/app/services/power-plant.service';
import { PowerPlant } from 'src/app/models/power-plant.model';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

    //coordinates for Brasov
  private latitude: number = 45.6427;
  private longitude: number = 25.5887;
  
  marker!: CustomPinMarker ;
  
  private map!: L.Map;
  private centroid: L.LatLngExpression = [this.latitude, this.longitude];
  powerPlantList!: PowerPlant[];

  ngOnInit(): void {
    this.initMap();
  }
  
  constructor(private powerPlantService: PowerPlantService) {

  }

  private initMap(): void {
    this.map = L.map('map', {
      center: this.centroid,
      zoom: 2.8
    });

    const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
    {
      minZoom: 2.8,
      attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });

    tiles.addTo(this.map);
  
     /*
    the get request made through the HttpClient call will generate an Observable type to which we have to subscribe and unsubscribe at the end.
    */

    this.powerPlantService.getAll().subscribe(powerPlantArrayFromGet => {
      console.log(powerPlantArrayFromGet);
      this.powerPlantList = powerPlantArrayFromGet;
      this.powerPlantList.forEach( (element) => {
          if (element.published) {
            this.marker = new CustomPinMarker([element.latitude!, element.longitude!], element.gppd_idnr).on('click', this.onPinClick).addTo(this.map);
                       
          }          
      });
    })
  }

  onPinClick() : void{
       console.log("In pinclick method");
       let observableVar! : Observable<PowerPlant>;
       let powerPlant! : PowerPlant;
       observableVar = this.powerPlantService.findByGPPD_INDR(this.marker.getId());

       observableVar.subscribe(data => {powerPlant = data});
       this.marker.bindPopup(""+ powerPlant.powerPlant_name + '<br\>' + powerPlant.country_name + '<br\>' + powerPlant.est_generation_gwh_2017
      + '<br\>' + powerPlant.primaryFuel ).openPopup();
  }
}

export class CustomPinMarker extends L.Marker {
   gppd_idnr: String | undefined;

  constructor(latLng: L.LatLngExpression, gppd_idnr: string | String | undefined, options?: L.MarkerOptions) {
    super(latLng, options);
    this.gppd_idnr = gppd_idnr;
  }

  getId() : any{
    return this.gppd_idnr;
  }
}

正如我在评论中提到的,问题在于误解了 this 在您的点击处理程序中的含义。使用时

this.marker = new CustomPinMarker([element.latitude!, element.longitude!], element.gppd_idnr)
  .on('click', this.onPinClick)
  .addTo(this.map);

您正在将函数 onPinClick 的内容传递给事件处理程序。在您的事件处理程序中,this 是触发事件的元素。所以它是 CustomPinMarker DOM 对象。

但是,您可以通过将函数包装在箭头函数中以将其传递给事件处理程序来规避此问题。

this.marker = new CustomPinMarker([element.latitude!, element.longitude!], element.gppd_idnr)
  .on('click', () => {
      this.onPinClick();
  })
  .addTo(this.map);

之所以可行,是因为箭头函数没有自己的 this 绑定。这些函数在声明它们的范围内解析。在您的情况下,HomeComponent。

请记住,这样做显然会失去访问触发元素的能力。如果您需要该元素,您可以为您的 HomeComponent 添加别名,并在事件处理程序的匿名函数中使用它:

// that = HomeComponent, this = triggering DOM element
const that = this;
this.marker = new CustomPinMarker([element.latitude!, element.longitude!], element.gppd_idnr)
  .on('click', function() {
     let observableVar! : Observable<PowerPlant>;
     let powerPlant! : PowerPlant;
     observableVar = that.powerPlantService.findByGPPD_INDR(that.marker.getId());

   observableVar.subscribe(data => {powerPlant = data});
   that.marker.bindPopup(""+ powerPlant.powerPlant_name + '<br\>' + powerPlant.country_name + '<br\>' + powerPlant.est_generation_gwh_2017
  + '<br\>' + powerPlant.primaryFuel ).openPopup();
  })
  .addTo(that.map);

修改后的正确答案:

import { Component, OnInit } from '@angular/core';
import * as L from 'leaflet';
import { PowerPlantService } from 'src/app/services/power-plant.service';
import { PowerPlant } from 'src/app/models/power-plant.model';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
 //coordinates for Brasov
  private latitude: number = 45.6427;
  private longitude: number = 25.5887;
   
  private map!: L.Map;
  private centroid: L.LatLngExpression = [this.latitude, this.longitude];
  powerPlantList!: PowerPlant[];

  ngOnInit(): void {
    this.initMap();
  }
  
  constructor(private powerPlantService: PowerPlantService) {  }

  private initMap(): void {
    this.map = L.map('map', {
      center: this.centroid,
      zoom: 2.8
    });

    const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
    {
      minZoom: 2.8,
      attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    });

    tiles.addTo(this.map);
  
    // this queries the database and retrieves a list of all the elements
    this.powerPlantService.getAll().subscribe(powerPlantArrayFromGet => {
      console.log(powerPlantArrayFromGet);
      this.powerPlantList = powerPlantArrayFromGet;
   // this loops through the list and creates and places a pin on the map at the location of each element
      this.powerPlantList.forEach( (element) => {
          if (element.published) {
            const that = this;
            //console.log(element.gppd_idnr + " 1")  ;  
            // this create the pin to be placed on the map and attaches to it a pop-up that, when clicked, will retrieve from the database information about that item and display it on the pop-up      
            let marker = new L.Marker([element.latitude!, element.longitude!])
              .on('click', function() {
                let observableVar! : Observable<PowerPlant>;
                let powerPlant! : PowerPlant;
                //console.log(element.gppd_idnr + " 2");
                observableVar = that.powerPlantService.findByGPPD_INDR(element.gppd_idnr + "");
            // the is where the interogation is done (above) and the pop-up is populated with the relevant information
                observableVar.subscribe(
                  data => 
                  {
                    powerPlant = data
            
                    var latlong : L.LatLng = new L.LatLng(<number> powerPlant.latitude, <number> powerPlant.longitude);

                    marker.setLatLng(latlong).bindPopup( 
                      powerPlant.powerPlant_name + 
                      '<br\>' + 
                      powerPlant.country_name + 
                      '<br\>' + 
                      powerPlant.est_generation_gwh_2017 + 
                      '<br\>' + 
                      powerPlant.primaryFuel )
                      .openPopup();
                  })
              })
              .addTo(that.map);                       
          }          
      });
    })
  }
}