Home > Back-end >  Angular send JSPDF file to API to store it in database
Angular send JSPDF file to API to store it in database

Time:12-12

I have a form in my project where I enter user data and generate JSPDF file with these data and I need to make send the PDF generated file to the 'report' FormControl to send it to the API so I can store it in the database.

I have a blob variable that will carry the blob, when I submit the form I am calling the exportHtmlToPDF() function the converts the div to PDF and output it as blob to the blob variable

blob: any;

createUserForm: FormGroup = new FormGroup({
    'name': new FormControl(null, [Validators.required, Validators.minLength(3), Validators.maxLength(100)]),
    'gender': new FormControl(null, [Validators.required]),
    'date_of_birth': new FormControl(null, [Validators.required]),
    'identity': new FormControl(null, [Validators.required, Validators.minLength(9), Validators.maxLength(14)]),
    'patient_id': new FormControl(null, [Validators.required, Validators.minLength(3), Validators.maxLength(100)]),
    'test': new FormControl(null, [Validators.required, Validators.minLength(3), Validators.maxLength(100)]),
    'result': new FormControl(null, [Validators.required]),
    'collect_date': new FormControl(null, [Validators.required]),
    'report_date': new FormControl(null, [Validators.required]),
    'branch': new FormControl(null, [Validators.required, Validators.minLength(3), Validators.maxLength(100)]),
    'reference_range': new FormControl(null, [Validators.required]),
    'lab_director': new FormControl(null, [Validators.required, Validators.minLength(3), Validators.maxLength(100)]),
    'qr_url': new FormControl(this.qrCode),
    'qr_link': new FormControl(this.qrLink),
    'report': new FormControl(null)
});

onSubmit() {
    this.exportHtmlToPDF();

    let formData = new FormData();
    formData.append('name', this.createUserForm.get('name')?.value);
    formData.append('gender', this.createUserForm.get('gender')?.value);
    formData.append('date_of_birth', this.createUserForm.get('date_of_birth')?.value);
    formData.append('identity', this.createUserForm.get('identity')?.value);
    formData.append('patient_id', this.createUserForm.get('patient_id')?.value);
    formData.append('test', this.createUserForm.get('test')?.value);
    formData.append('result', this.createUserForm.get('result')?.value);
    formData.append('collect_date', this.createUserForm.get('collect_date')?.value);
    formData.append('report_date', this.createUserForm.get('report_date')?.value);
    formData.append('branch', this.createUserForm.get('branch')?.value);
    formData.append('reference_range', this.createUserForm.get('reference_range')?.value);
    formData.append('lab_director', this.createUserForm.get('lab_director')?.value);
    formData.append('qr_url', this.createUserForm.get('qr_url')?.value);
    formData.append('qr_link', this.createUserForm.get('qr_link')?.value);
    formData.append('report', this.blob);

    this._UsersService.storeUser(formData).subscribe(data => {
        if (data == "Customer Added Successfully") {
            alert('customer added!')
        }
    });
}

public exportHtmlToPDF() {
    let data = document.getElementById('pdf')!;

    html2canvas(data).then(canvas => {

        let docWidth = 208;
        let docHeight = canvas.height * docWidth / canvas.width;

        const contentDataURL = canvas.toDataURL('image/png');
        let doc = new jsPDF('p', 'mm', 'a4');
        let position = 0;
        doc.addImage(contentDataURL, 'PNG', 0, position, docWidth, docHeight);

        this.blob = doc.output('blob');
    });
}

CodePudding user response:

I hate to have to repeat myself, but: DO NOT USE toDataURL FOR ANYTHING LARGER THAN A FEW KILOBYTES:

  • Generating data: URIs is sloooow and blocks the browser's UI/render thread (e.g. my browser on my beefy modern machines freezes for about 5-7 seconds when it saves a Blob containing a 4K-wallpaper-sized JPEG image to a Base64 data: URI, whereas using URL.createObjectURL is instantaneous.
  • Base64 encoding increases the size of your binary data by 33% (as it uses 4 bytes of ASCII text to represent 3 raw binary bytes).
  • Every time a data: URI is used it has to be validated, parsed, decoded, and its inner binary data copied into a new destination buffer (internal to the browser). This is a whole slew of inefficencies. Using Blob avoids that entirely.
  • You cannot use data: URIs to directly upload binary data unless your server-side or back-end logic is specifically programmed to handle it (also there's no official MIME type that represents a data: URI alone, and the old fallback of application/octet-stream isn't appropriate either), whereas if you pass a Blob to fetch or XMLHttpRequest it will perfectly upload/transfer the binary data with the correct MIME Content-Type too!

...with that out of the way:

<canvas> supports exporting image data as a Blob object which you can pass directly into a FormData object for multipart/form-data binary uploads (just like <input type="file" />) - as well as for direct PUT requests where the request body's payload is the raw Blob data.

Apparently jsPDF's addImage function accepts a HTMLCanvasElement directly, so there is no need to use toBlob (and especially not toDataURL, eww).

async function exportHtmlToPDF() {

    const pdfElement = document.getElementById('pdf') as /* What element is it? */ | null;
    if( !pdfElement ) throw new Error( "Couldn't find 'pdf' element." );

    const canvas = await html2canvas( pdfElement );

    let docWidth = 208;
    let docHeight = canvas.height * docWidth / canvas.width;
    
    const doc = new jsPDF( 'p', 'mm', 'a4' );
    
    doc.addImage(canvas, 'PNG', /*x:*/ 0, /*y:*/ 0, docWidth, docHeight);
    
    const pdfBlob = doc.output( 'blob' );
    
    const uploadResponse = await fetch( '/pdf-upload-handler', { method: 'PUT', body: pdfBlob } );
    console.log( uploadResponse );
}

However I noticed that your program doesn't actually need to generate a PDF. The PDF you're generating is just wrapping an existing PNG image, so why not just upload the PNG image and skip the (slooowwww) client-side PDF generation?

async function exportHtmlToPng() {

    const pdfElement = document.getElementById('pdf') as /* What element is it? */ | null;
    if( !pdfElement ) throw new Error( "Couldn't find 'pdf' element." );

    const canvas = await html2canvas( pdfElement );
    
    canvas.toBlob( gotBlob, 'image/png' );
}

async function gotBlob( pngBlob: Blob | null ) {
    
    if( !pngBlob ) return;

    const uploadResponse = await fetch( '/png-image-upload-handler', { method: 'PUT', body: pngBlob } );
    console.log( uploadResponse );
}

Better yet, here's a version with a Promise adapter for toBlob:

async function exportHtmlToPng() {

    const pdfElement = document.getElementById('pdf') as /* What element is it? */ | null;
    if( !pdfElement ) throw new Error( "Couldn't find 'pdf' element." );

    const canvas = await html2canvas( pdfElement );
    
    const pngBlob = await getBlob( canvas, 'image/png' );

    const uploadResponse = await fetch( '/png-image-upload-handler', { method: 'PUT', body: pngBlob } );

    console.log( uploadResponse );
}

function getBlob( canvas: HTMLCanvasElement, type: string ): Promise<Blob> {
    
    return new Promise( function( resolve, reject ) {
        
        canvas.toBlob( function( blob ) => {
            
            if( blob ) resolve( blob );
            else reject( "Failed to create Blob from Canvas." );
            
        }, type );
        
    } );
}
  • Related