Search code examples
angulartypescriptleaflet

Error with leaflet on angular 17 standalone app


I'm a recent angular user. I'm trying to add a leaflet map to my project. I saw that I had to use a service to avoid errors related to window. I used the following example: https://github.com/NickToony/universal-starter-leaflet-example

I made my map.service.tst file like this:

import { Injectable, PLATFORM_ID, Inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@Injectable()
export class MapService {

  public L = null;

  constructor(@Inject(PLATFORM_ID) private platformId: Object) {
    if (isPlatformBrowser(platformId)) {
      this.L = require('leaflet');
    }
  }

}

and my map.component.ts file like this:

import { Component, OnInit } from '@angular/core';
import { MapService } from '../services/map.service';
import { Map } from 'leaflet';

@Component({
  selector: 'app-map',
  standalone: true,
  imports: [],
  templateUrl: './map.component.html',
  styleUrl: './map.component.css',
})

export class MapComponent implements OnInit {
  public message!: string;
  private map!: Map;

  constructor(private mapService: MapService) {}

  ngOnInit() {
    if (this.mapService.L) {
      // Leaflet is loaded - load the map!
      this.message = 'Map Loaded';
      this.setupMap();
    } else {
      // When the server renders it, it'll show this.
      this.message = 'Map not loaded';
    }
  }

  private setupMap() {
    if (!this.mapService || !this.mapService.L) {
      console.error('mapService is not initialized');
      return;
    }

    // Create the map in the #map container
    this.map = this.mapService.L.map('map').setView([51.505, -0.09], 13);

    // Add a tilelayer
    this.mapService.L.tileLayer(
      'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
      {
        attribution:
          'copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>,' +
          ' Tiles courtesy of <a href="http://hot.openstreetmap.org/" target="_blank">Humanitarian OpenStreetMap Team</a>'
      }
    ).addTo(this.map);

  }
}

in map.component.ts, I have two errors:

  • The 'map' property doesn't exist on the 'never' type.
  • The 'tileLayer' property doesn't exist on the 'never' type.

I can't figure out how to get past these two errors.


Solution

  • Nick Felker already provided a great answer why you get these errors. You may also be able to use @types/leaflet instead of writing your own typings.

    However, you don't really need that service. Here is another example using Angular with Leaflet:

    1. Create Angular app with ng new leaflet-angular-example
    2. Install Leaflet with npm install leaflet
    3. Install typings for Leaflet with npm install --save-dev @types/leaflet
    4. Create a component with ng g component map
    5. Change app.component.html
    <main>
      <router-outlet></router-outlet>  
    </main>
    
    1. Change app.routes.ts
    import { Routes } from '@angular/router';
    import { MapComponent } from './map/map.component';
    
    export const routes: Routes = [
        { path: 'map', component: MapComponent },
        { path: '', redirectTo: '/map', pathMatch: 'full' },
    ];
    
    1. Change styles.scss
    body {
        height: 100vh;
        width: 100wh;
        margin: 0;
    }
    
    1. Update angular.json by adding node_modules/leaflet/dist/leaflet.css to styles
    {
      "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
      "version": 1,
      "newProjectRoot": "projects",
      "projects": {
        "leaflet-angular-example": {
          // ...
          "architect": {
            "build": {
              "builder": "@angular-devkit/build-angular:application",
              "options": {
                // ...
                "styles": [
                  "src/styles.scss",
                  "node_modules/leaflet/dist/leaflet.css"  // <-- add this line
                ],
                // ...
    
    1. Change map.component.html
    <div id="map" #map>
    </div>
    
    1. Change map.component.scss
    #map {
        height: 100vh;
        width: 100vw;
    }
    
    1. Change map.component.ts
    import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
    import { Map, map, tileLayer } from 'leaflet';
    
    @Component({
      selector: 'app-map',
      standalone: true,
      imports: [],
      templateUrl: './map.component.html',
      styleUrl: './map.component.scss'
    })
    export class MapComponent implements AfterViewInit {
    
      @ViewChild('map')
      mapElementRef: ElementRef = null!;
    
      private _map: Map = null!;
    
      ngAfterViewInit(): void {
    
        this._map = map(this.mapElementRef.nativeElement)
            .setView([46.801111, 8.226667], 8);
    
        tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
            maxZoom: 19,
            // add a link to OpenStreetMap (omitted here for shorter line width)
            attribution: '&copy; OpenStreetMap'
        }).addTo(this._map);
    
      }
    
    }