Search code examples
angulartypescriptservicebase64url

How to pass an image base64 url to a sibling component


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.


Solution

  • 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