javascriptangularfile

File, Blob, Promise, async/await, store images in FormData, encode/decode images in base64 .. in Typescript/HTML component - Angular 19


I have this snippet of code.. i want to know where i'm wrong.. i save an image, that i have stored inside my project, into a new File.. but when i try to reconvert it to an image i get an empty image..

function selectImagesToUpload() {
  const input = document.getElementById("product_pics");
  const preview = document.querySelector(".preview");
  const images = preview.querySelectorAll("div.preview > p");

  images.forEach(async item => {

    let fn = item.innerText.split(';')[0];
    let ext = fn.slice(fn.lastIndexOf('.') + 1, fn.length);

    let p = await fetch(fn).then(response => response.blob());
    let b = await p.arrayBuffer();
    let f0 = new File(new Uint8Array(b), fn, { type: `image/${ext}` });

    var reader = new FileReader();
    reader.onload = function (e) {
      var image = document.createElement("img");
      image.src = e.target.result;
      document.body.appendChild(image);
    }
    reader.readAsDataURL(f0);

  });
}

This is an example paragraph with the relative url (i set it in the assets of the project) : < p > img/prodotto7.jpg;< /p >

This is the result of the append to the dom : empty image

Can someone help me? This is like a test, i must do it work to go on with my project..

Investigating with some crazy tricks i get a "Uncaught InvalidCharacterError: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded." on the line "atob(e.target.result);" so the FileReader isn't doing it's work correctly..

let fn = item.innerText.split(';')[0];
let ext = fn.slice(fn.lastIndexOf('.') + 1, fn.length);
let p = await fetch(fn).then(response => response.blob());
let b = await p.arrayBuffer();
let ua = new Uint8Array(b)
let u64 = ua.toBase64;
let f0 = new File( ua , fn, { type: `image/${ext}` });
console.log(f0);
const para = document.createElement("p");
para.textContent = item.textContent;
preview.appendChild(para);

var reader = new FileReader()

reader.onload = function (e) {
  var image = document.createElement("img");
  image.src = e.target.result;
  console.log(image.src);
  var bs = atob(e.target.result);
  var buffer = new ArrayBuffer(bs.length);
  var ba = new Uint8Array(buffer);
  for (var i = 0; i < bs.length; i++) {
    ba[i] = bs.charCodeAt(i);
  }

  var blob = new Blob([ba], { type: "image/jpg" });
  document.body.appendChild(URL.createObjectURL(blob));
}
reader.readAsDataURL(f0);


Solution

  • SOLVED (i was needing mechanisms of File variables - transforming images and then get them back - with the scope to store images into a File[] or a FileList, then inside a FormData variable, with the final scope to post and get messages with a microservice, mission complete.. and now i added the synchronization!!)

    service.ts

    import { HttpClient } from "@angular/common/http";
    import { Inject, Injectable } from "@angular/core";
    import { DOCUMENT } from '@angular/common';
    
    @Injectable({
      providedIn: 'root'
    })
    
    export class GestioneProdottiService {
    
      formData !: FormData;
    
      fileTypes = [
        "image/apng",
        "image/bmp",
        "image/gif",
        "image/jpeg",
        "image/pjpeg",
        "image/png",
        "image/svg+xml",
        "image/tiff",
        "image/webp",
        "image/x-icon",
      ];
    
      constructor(private httpClient: HttpClient,
        @Inject(DOCUMENT) private document: Document) { }
    
      insImages = (data: FormData) => {
        //return this.httpClient.post<ApiMsg>(`http://${this.server}:${this.port}/url`, articolo);
      }
    
      getFormDataEntries() {
        for (const pair of this.formData.entries())
          console.log(pair[0], pair[1]);
      }
    
      selectImagesToUpload() {
        return new Promise<File[]>(async (resolve) => {
          console.log('promise');
          const input = this.document.getElementById("product_pics");
          const preview = this.document.querySelector(".preview");
          const images = preview?.querySelectorAll("div.preview > p");
          const arrSupp = new Array();
    
          images?.forEach(item =>
            arrSupp.push(item));
    
          let fl: File[] = new Array();
          //console.log(fl);
    
          await Promise.all(arrSupp.map(async item => {
            if (preview?.firstChild === undefined) {
              console.log('primo');
              /*   *** TODO ***
              const para = document.createElement("p");
              console.log(item.innerText);
              para.textContent = item.textContent;
              preview.appendChild(para);
              fl[i] = */
            } else {
              console.log('secondo');
              //console.log(item.textContent);
              let fn = item?.textContent?.split(';')[0];
              let ext = fn?.slice(fn.lastIndexOf('.') + 1, fn.length);
              if (fn) {
                let p = await fetch(fn).then(response => response.blob());
                let b = await p.arrayBuffer();
                let ua = new Uint8Array(b)
                //console.log(ua)
    
                //let b64 = ua.toBase64();  //it does not work in some of the most widely-used browsers.
    
                const bin = [];
                for (let i = 0; i < ua.length; i++) {
                  bin.push(String.fromCharCode(ua[i]));
                }
                const b64encoded = btoa(bin.join(""));
                //console.log(b64encoded);
    
                const trimmedString = b64encoded.replace('dataimage/jpegbase64', '');
                const imageContent = atob(trimmedString);
                const buffer = new ArrayBuffer(imageContent.length);
                const view = new Uint8Array(buffer);
    
                for (let n = 0; n < imageContent.length; n++) {
                  view[n] = imageContent.charCodeAt(n);
                }
                const type = `image/${ext}`;
                const blob = new Blob([buffer], { type });
                let f0 = new File([blob], fn, { lastModified: new Date().getTime(), type });
    
                var image = this.document.createElement("img");
                image.src = `data:image/png;base64, ${b64encoded}`;
                //console.log(image.src)
                //console.log(f0);
                this.document.body.appendChild(image);  //img show!
    
                this.Main(f0);
    
                const para = this.document.createElement("p");
                console.log(`filename : ${fn};`);
                para.textContent = `${fn};`;
                preview.appendChild(para);
    
                fl.push(f0);
              }
            }
          }));
    
          while (preview?.firstChild) {
            preview.removeChild(preview.firstChild);
          }
    
          const curFiles = (<HTMLInputElement>input)?.files;
          if (curFiles?.length === 0) {
          } else {
            if (curFiles) {
              for (const file of curFiles) {
                if (this.validFileType(file)) {
                  let filename = String(file.name);
                  let image = filename.slice(0, filename.lastIndexOf('.'));
                  if (this.validImage(image)) {
                    const para = this.document.createElement("p");
                    //console.log(`filename : ${file.name};`);
                    para.textContent = `${filename};`;
                    preview?.appendChild(para);
    
                    fl.push(file);
                    this.Main(file);
                  }
                }
              }
            }
          }
          resolve(fl);
        })
      }
    
      async f() {
        console.log("calling");
    
        const result = await this.selectImagesToUpload().then(
          (fl: File[]) => {
            let fd = new FormData();
            console.log('1then');
            console.log(fl);
    
            for (let i = 0; i < fl.length; i++) {
              fd.append(fl[i].name, fl[i]);
            }
    
            this.formData = fd;
    
          },
          (err) => {
            console.log('errore');
            console.log(err);
          });
      }
    
      validFileType(file: File) {
        return this.fileTypes.includes(file.type);
      }
    
      validImage(image: string) {
        return !image.includes(';') && !image.includes('.');
      }
    
      clearAllImages() {
        const preview = this.document.querySelector(".preview");
        const images = preview?.querySelector("div.preview > p");
        while (preview?.firstChild) {
          preview.removeChild(preview.firstChild);
        }
        this.formData = new FormData();
      }
    
      async Main(file: any) {
        const toBase64 = (file: any) => new Promise((resolve, reject) => {
          const reader = new FileReader();
          reader.readAsDataURL(file);
          reader.onload = () => {
            resolve(reader.result);
          }
          reader.onerror = reject;
        });
    
        let b64 = await toBase64(file);
        //console.log(b64);
        var image = this.document.createElement("img");
        image.src = String(b64);
    
        //let u8 = Uint8Array.fromBase64(b64);  //it does not work in some of the most widely-used browsers.
    
        this.document.body.appendChild(image);  //img show!
      }
    
    }

    component.ts

    ...
    @ViewChild('product_pics')
    productPicsInput!: ElementRef;
    
    constructor(private gestioneProdotti: GestioneProdottiService) {}
    ...
    
    selectImagesToUpload($event : any) {
      this.gestioneProdotti.f();
    }
    
    clearValues($event: any) {
      this.gestioneProdotti.clearAllImages();
      const input = this.productPicsInput.nativeElement
      input.value = "";
    }

    component.html

    <!-- Immagine -->
    <div class="mb-4">
        <label class="form-label">Immagine:</label>
        <input #product_pics class="form-control" type="file"
               enctype="multipart/form-data"
               id="product_pics"
               name="product_pics"
               accept=".jpg, .jpeg, .png"
               (change)="selectImagesToUpload($event)" multiple />
        <!application/x-www-form-urlencoded-->
        <button type="button" class="btn btn-primary" (click)="clearValues($event)">Clear all</button>
    </div>
    <div id="uploadImages" class="preview">
        @if(articolo.imgsrc){
        <p>{{articolo.imgsrc}};</p>
        }@else{
        <p>No files currently selected for upload</p>
        }
    </div>

    image shown, forget about layout.. look at the substance xD