0

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.

alim1990
  • 4,656
  • 12
  • 67
  • 130
  • I think just replacing `var` with `let` will solve your problem, but I don't think this is how you should do it. Never run async calls in a loop. – Ashish Ranjan Dec 17 '18 at 11:56
  • @xyz okay. Can you tell me how to do it ? – alim1990 Dec 17 '18 at 11:57
  • @xyz I mean about async calls – alim1990 Dec 17 '18 at 11:58
  • @xyz it didn't work with `var` – alim1990 Dec 17 '18 at 11:59
  • Lil busy, I will add an answer in a while if it hasn't already been answered and what did you replace? Replace `var elem = docum..` with `let elem = doc...`. It has it work. `let` gives block scope to variables so every loop is a new block while `var` gives a function scope so the same element the rewritten and the last value is used to download your image. Also replace `var generatedImage` with `let generatedImage` – Ashish Ranjan Dec 17 '18 at 12:11
  • @xyz I tried changing all `var` to `let` but still the same issue. I will wait for your answer. I guess, no one will answer this question. – alim1990 Dec 17 '18 at 12:17
  • @xyz you still buzzy man ? I really need your help. – alim1990 Dec 18 '18 at 07:37
  • On it! Please show how your `array` looks like, I see you are looping twice. I will frame an answer with a normal array for now. – Ashish Ranjan Dec 18 '18 at 08:42

1 Answers1

1

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

Ashish Ranjan
  • 12,760
  • 5
  • 27
  • 51
  • Error saying: `ERROR TypeError: Cannot read property 'ownerDocument' of null at html2canvas` I think it is pointing to `docElem` from the first implementation – alim1990 Dec 18 '18 at 09:20
  • And if I console `this.array` I will get `[Object Object]` instead of the real value – alim1990 Dec 18 '18 at 09:37
  • The array contain 5 fields: `id`, `name`, `name_other`, `gender`, and `dob`. – alim1990 Dec 18 '18 at 09:38
  • The second implementation is not working as well as it is not reading `array[index].toString()` – alim1990 Dec 18 '18 at 10:14
  • @alim1990: Please show the contents of your array, of course it will not work if it is not a simple array as given in the example, also refer the stackblitz link added in the answer – Ashish Ranjan Dec 18 '18 at 10:58
  • Okay let me show you the full array as I didn't make the stackblitz: https://imgur.com/a/XX6TgNB – alim1990 Dec 18 '18 at 11:17
  • @alim1990: Sorry for the late replies, now show the HTML what ids they contain.(the one which you are trying to download) You can update your question itself with all relevant details. – Ashish Ranjan Dec 18 '18 at 11:42
  • Okay, check the previous comment, while I am working on a stackblitz. – alim1990 Dec 18 '18 at 11:43
  • @alim1990: Yeah, stackblitz would be the best. From your comments, if you do `from(Object.keys(this.array)).pipe......` in the first implementation in the answer, your code should work – Ashish Ranjan Dec 18 '18 at 11:47
  • Oh it worked. But I prefer the recursive method as it does not need the Rx library – alim1990 Dec 18 '18 at 11:50
  • 1
    Okay, great! Implementation depends on you. – Ashish Ranjan Dec 18 '18 at 11:53
  • Is it good to use RxJS in a project ? This is the first component, whereas I use it in the whole project and I am afraid that it is going to increase the bundle size (I am using lazy loading). – alim1990 Dec 18 '18 at 11:56
  • @alim1990 If you are making an angular project, then `rxjs` will help you a lot!! There are so many thing which promises can't do, one way or another you will turn to using `rxjs`. Using `rxjs` in this example may be unnecessary if we can do it with a simple logic then ofcourse we can avoid it. – Ashish Ranjan Dec 18 '18 at 12:01
  • I know. That's why I am trying to use the simple logic method. I will get back to you with stackblitz – alim1990 Dec 18 '18 at 12:04
  • Here is the stackblitz with the exact array of mine using the recursive method. please check it https://stackblitz.com/edit/download-div-using-canvas-rjoxqx – alim1990 Dec 18 '18 at 12:07
  • @alim1990 see this working example: https://stackblitz.com/edit/download-div-using-canvas-5c53ha?file=src%2Fapp%2Fapp.component.ts. – Ashish Ranjan Dec 18 '18 at 12:17