0

I have a component that renders leaflet map perfectly and I can draw some polygons on it. I added htmlToImage library to take a screenshot of what I have drawn and if I append it as a child to the same component, it will render fine. But I like to pass that image source to the next component(sibling component) and do other stuff on it. I tried to implement a service that my first component writes the dataURL as a string variable and the second component gets that variable and use it to display image from that stored path. The first component routes to the next one with router-link method as a sibling component (going from home/map to home/image) but it won't show the image and simply the console.log(path) is empty string which is what I initialized in my service, originally. What am I doing wrong?

I am using Angular 9 and running on local host. Here is what I have tried to so far:

map.component.ts

// Map related methods ....
// ....

getScreenshot() {
   htmlToImage.toPng(document.getElementById('map'))
      .then((dataUrl) => {
        this.projectService.setProject('image', dataUrl);
      })
      .catch((error) => {
        console.error('oops, something went wrong!', error);
      });
}

screenshot.component.ts

const path = this.projectService.getProject().image;

constructr(private projectService: ProjectService) {}

screenshot.component.html

<div class="row content">
    <img [src]="path" alt="map" >
</div>

project.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})

export class ProjectService {

  private project = {
    title: '',
    image: '',

  };

  getProject() {
    return this.project;
  }

  setProject(option: any, value: any) {
    this.project[option] = value;
  }

}

app-routing.module.ts

export const appRoutes: Routes = [
  {
    path: 'home', component: AppComponent,
    children: [
      { path: 'map', component: MapComponent },
      { path: 'image', component: ScreenshotComponent },
    ]
  },
];

I have posted my code on StackBlitz.

  • Is your ProjectService a singleton? Can you post the full code of your ProjectService? – bjdose Feb 22 '20 at 01:59
  • I think so, I updated the code part for my service. – Silevrmind28 Feb 24 '20 at 17:23
  • Yes, I can see your `ProjectService` has `providedIn: 'root'`. Have you included this service into the provider's array of a module? Can you upload the code on Stackblitz? That way I can check what is happening. – bjdose Feb 24 '20 at 17:30
  • I tried to copy it [here](https://stackblitz.com/edit/angular-uqf3yy) but stackblitz can't load the map. – Silevrmind28 Feb 24 '20 at 23:44

1 Answers1

0

I created an example map into MapComponent and followed the documentation of ngx-leaflet-draw and ngx-leaflet in order to draw a polygon inside a map. After creating a polygon in /home/map route I stored the image with the polygon into ProjectService for getting it into ScreenshotComponent after going to /home/image route through the button with the routerLink directive.

I tried to be descriptive with the method names in order to see easily what is happening.

map.component.html

<div style="height: 300px; width: 600px;"
     id="map"
     leaflet 
     leafletDraw
     (leafletMapReady)="onMapReady($event)"
     (leafletDrawCreated)="onLeafletDrawCreated($event)"
     [leafletOptions]="leafletOptions"
     [leafletDrawOptions]="drawOptions"
     >
</div>
<div>Draw a polygon and go to image route</div>
<button routerLink="/home/image">Go Image Page</button>

map.component.ts

import { Component } from "@angular/core";
import htmlToImage from "html-to-image";
import { ProjectService } from "../project.service";
import * as L from 'leaflet';

@Component({
  selector: "app-map",
  templateUrl: "./map.component.html",
  styleUrls: ["./map.component.css"]
})
export class MapComponent {

  map: L.Map;
  editableLayers = new L.FeatureGroup();
  leafletOptions = {
    layers: [
      L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18 })
    ],
    zoom: 5,
    center: L.latLng(46.879966, -121.726909)
  };
  drawOptions = {
    position: 'topright',
    draw: {
        marker: {
          icon: L.icon({
              iconSize: [ 25, 41 ],
              iconAnchor: [ 13, 41 ],
              iconUrl: 'assets/marker-icon.png',
              shadowUrl: 'assets/marker-shadow.png'
          })
        },
        polyline: false,
        circle: {
            shapeOptions: {
                color: '#aaaaaa'
            }
        }
    }
  };

  constructor(private projectService: ProjectService) {}

  onMapReady(map: L.Map) {
    // map loaded, store in map var
    this.map = map;
  }

  onLeafletDrawCreated(event: any) {
    // after drawing a polygon take a screenshot
    this.getScreenshot();
  }

  getScreenshot() {
    // I recommend you use Renderer2 provided by Angular Core Package instead of document.getElementById directly
    // https://angular.io/api/core/Renderer2
    htmlToImage
      .toPng(document.getElementById("map")) 
      .then(dataUrl => {
        this.projectService.setProject("image", dataUrl);
      })
      .catch(error => {
        console.error("oops, something went wrong!", error);
      });
  }
}

screenshot.component.html

<div *ngIf="path; else noImageTemplate;" 
    style="height: 300px; width: 600px;" class="row content">
    <img style="height: 100%; width: 100%; object-fit: contain;" [src]="path" alt="map" >
</div>
<ng-template #noImageTemplate>
  <div style="height: 300px; width: 600px; border: 1px solid lightgray;">
    No image yet, try select some polygon on map page
  </div>
</ng-template>
<button routerLink="/home/map">Go Map Route</button>

screenshot.component.ts

import { Component, OnInit } from '@angular/core';
import { ProjectService } from "../project.service";

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

  path: string = this.projectService.getProject().image;

  constructor(private projectService: ProjectService) {}

  ngOnInit() {
    console.info('path from project service!', this.path);
  }
}

project.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ProjectService {
  private project = {
    title: '',
    image: '',
  };
  getProject() {
    return this.project;
  }
  setProject(option: any, value: any) {
    this.project[option] = value;
  }
}

Solution available here: Stackblitz

bjdose
  • 1,269
  • 8
  • 10
  • Thanks for your explanation. I now have a better understanding of provider concept. But unfortunately, after trying your recommendation, it didn't work. I think there is something wrong with handling map data because I have input fields in my project that I wrote on that service such as title and it does pass the changes to the next component but it cannot pass any data related to the map. I tried to get the lat & lng of the map or the drawn polygons, pass it in a variable in service and receive that in next component and I get the values that I have originally initialized in the service. – Silevrmind28 Feb 25 '20 at 17:55
  • I do not have parent and child components. They are siblings components. So, I want to do stuff on the map, then get those information and a screenshot and then go to next component (a sibling of the first one), display the image and do other stuff with those info taken from map in previous component. – Silevrmind28 Feb 25 '20 at 22:45
  • Do I need to subscribe to Observable instead of using service? – Silevrmind28 Feb 25 '20 at 23:12
  • Yes, you do. I edited my answer with an example with observable but you need a singleton service as well. Let me know if you can resolve your issue with that. – bjdose Feb 26 '20 at 00:17
  • Your answer still shows the content of screenshot.component.html. How can I get the image only if sibling.component.html has an image element with the path from screenshot component but not showing the map anymore. Basically I want to replace the map element after taking the screenshot and would see the sibling image which the src has been taken from screenshot component. – Silevrmind28 Feb 26 '20 at 01:33
  • I see, but you have to create another question on StackOverflow because the first question already was answered. – bjdose Feb 26 '20 at 02:14
  • No, that was my question from the beginning! – Silevrmind28 Feb 26 '20 at 17:12
  • Ok, could you update your question and be more specific adding the additional information that you wrote in the comments, please? – bjdose Feb 26 '20 at 17:15
  • I can't see the map in Stackblitz link, even the project is not working. Can you fix the errors and allow to me replicate the problem? – bjdose Feb 27 '20 at 20:50
  • I could use an example map because of google maps had an error, I just posted my solution. – bjdose Feb 27 '20 at 22:09
  • I made it work with Google map inside my project. Not sure why it doesn't load properly on StackBlitz but thanks a lot!! – Silevrmind28 Feb 28 '20 at 18:23
  • You're very welcome! I'm glad to know I could help you! – bjdose Feb 28 '20 at 20:49
  • I tried to add a marker after creating the polygon on the center of it and I like to do the same behavior when I click on that marker icon on top of that layer(polygon) instead of a button on the map like your code. But when I do that, nothing will be passed to the next component. Do you know why? – Silevrmind28 Mar 02 '20 at 23:59
  • I think you have an issue adding your service, you need to have a singleton. Try to verify that your service is not added into providers array of any module or component. Your service only needs to be set proviededIn: 'root' – bjdose Mar 03 '20 at 03:24
  • I did changed that as you told me in the beginning. I double checked it again and it is set as proviededIn: 'root' but still not working. So, if I have a button on the map that just redirects to next component (as your solution code), it does save the data on the service & I can retrieve them in next component, but if I create a button on top of the polygon and that will only redirect to the next component, data will be null in next component. – Silevrmind28 Mar 03 '20 at 18:58
  • Ok, I got it. Can you post some code or something in order to see that behavior? Maybe you can create another question because this post is already resolved – bjdose Mar 03 '20 at 19:39