Search code examples
angulartypescripttypescript2.0html2canvasangular7

Angular 7 and html2Canvas iterating an array using foreach always download the last found index


I am iterating over an array in typescript to take a snapshot of a div having id equal to the index of the array and download an image file.

If the array was having 2 rows, 2 images should be downloaded. Here is the script:

public toCanvas() {
    let i = Object.keys(this.array).length;

    Object.keys(this.array).forEach((key, index)=>{
               var elem = document.getElementById(index.toString());

console.log(index)
      html2canvas(elem).then(function(canvas) {
        var generatedImage = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
        window.location.href=generatedImage;
      });


    })
  }

Using Object.keys(this.array).forEach((key, index), I am iterating to get the index, and then find the document getElementId(index).

The problem is that it always download the last image.

And at the console, the indexes are consoled, at the beginning and then the image is downloaded:

enter image description here

The html script:

<mat-card [id]="i"  *ngFor="let arrayOfData of array; let i=index; "  class="example-card"  #matCard>

I tried using while inside forEach():

public toCanvas() {
    let i = Object.keys(this.array).length;
    Object.keys(this.array).forEach((key, index) => {
      while (i != -1) {
        i--;
        console.log(i)
        var elem = document.getElementById(index.toString());


        html2canvas(elem).then(function (canvas) {
          var generatedImage = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
          window.location.href = generatedImage;
        });
      }

    })
  }

And it's doing the same behavior. It consoles the index until it reaches to 0 and then downloads the image of the last detected <mat-card>

EDIT

Here is a stackblitz of the recursive method provided by @xyz.


Solution

  • Your implementation may be different, change the code accordingly. Considering the elements in the array is like: array = [0, 1, 2];. And elements in the array are the ids of divs that you want to download. So your divs are like:

    <div [id]="id" *ngFor="let id of array">
      I am div id: {{id}}
    </div>
    

    Implementation 1 You can leverage rxjs to download the divs in a sequence, lets make the array as an Observable and work on each value in the array before starting the next download(Will use concatMap for this).

      public downloadDivs() {
        from(this.array).pipe(
          concatMap((arrayElem) => {
            let docElem = document.getElementById(arrayElem.toString());
            return from(html2canvas(docElem).then(function (canvas) {
              let generatedImage = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
              let a = document.createElement('a');
              a.href = generatedImage;
              a.download = `${arrayElem}.png`;
              a.click();
              return `${arrayElem}.png`;
            }));
          })
        ).subscribe((imageName) => {
          console.log("Image downloaded", imageName);
        }) 
      }
    

    Your HTML has this button to trigger the call:

    <button (click)="downloadDivs()">Download divs using rxjs</button>
    

    Implementation 2 call the download of the next div only after the download of one div is complete. I have used recursion for this.

    public trivialDownload() {
      console.log("Downloading image one by one, without a loop");
      this._download(0, this.array);
    }
    
    // this method will keep calling itself until all the elements of the array are scanned
    private _download(index, array) {
      if (index >= array.length) {
        console.log("Done!")
      } else {
        let docElem = document.getElementById(array[index].toString());
          html2canvas(docElem).then((canvas) => {
            let generatedImage = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
            let a = document.createElement('a');
            a.href = generatedImage;
            a.download = `${array[index]}.png`;
            a.click();
            // at this point, image has been downloaded, then call the next download.
            this._download(index + 1, array)
          });
      }
    }
    

    See this link for the implementation: https://stackblitz.com/edit/download-div-using-canvas?file=src%2Fapp%2Fapp.component.ts