0

I've been hitting my head against this for a while. All I want to do is store an attachment in the db and retrieve it with its associated records. These functions actually seem to work fine, but displaying the image, or getting it into a format where it can be displayed has proved difficult to say the least.

I can put an image when creating the initial object with this structure

{
    "display_image": {
        "content_type": "image/jpeg;base64",
        "data": "/9j/4AAQSkZJRgABAQEAeAB4AAD/..."
    }
}

and the attachment is there when I get the record with the attachments: true flag, but whenever I try to use it as an image source with something like this:

   const reader = new FileReader()
   reader.readAsDataURL(image_data)
   reader.onloadend = () => {
      this.display_image = reader.result
   }

The readAsDataUrl throws this error

ERROR TypeError: Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'.

So I've tried to convert it to a blob using this method which is from davoclavo's gihub

   public convertB64toBlob(source, type): Blob {
      const byteCharacters = atob(source)
      const byteNumbers = new Array(byteCharacters.length);
      for (let i = 0; i < byteCharacters.length; i++) {
         byteNumbers[i] = byteCharacters.charCodeAt(i);
      }
      const byteArray = new Uint8Array(byteNumbers)
      const blob: Blob = new Blob([byteArray], { type, })
      return blob
   }

but that one's throwing this error

Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.

I'm stumped now, would love some help as this issue has brought development to a standstill. Thanks heaps.

RamblinRose
  • 4,883
  • 2
  • 21
  • 33
theSiberman
  • 441
  • 4
  • 14

1 Answers1

0

You don't need to do any decoding. Your title says you are using AngularJS which is not Angular (which you tagged). AngularJS is not recommended for new development so I assume you are dealing with some legacy app.

For AngularJS you are bumping up against the classic problem of events or promises landing outside of AngularJS's digest in which case the common solution is to use $scope.apply(). An easy to understand description of $scope.apply() look here.

If you are actually using Angular instead of AngularJS you would use zone.run instead, see this SO Q/A.

The snippet below is a bare bones AngularJS app that looks up a base64 encoded jpeg attachment and displays it once loaded.

As an aside, setting methods like this

reader.onloadend = () => {

Is not good practice and likely results in memory leaks. Instead create the event handler, use it and dispose of it when done, as in the snippet. For example for a one-shot event,

element.addEventListener("someEvent", () => {    
    // clean up don't, leak stuff
    element.removeEventListener("someEvent", this);
  });

const jpegData =
  `/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAEMuMjoyKkM6NjpLR0NPZKZsZFxcZMySmnmm8dT++u3U6eX//////////+Xp////////////////////////////2wBDAUdLS2RXZMRsbMT//+n/////////////////////////////////////////////////////////////////////wgARCABAAEADAREAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAP/xAAWAQEBAQAAAAAAAAAAAAAAAAAAAQP/2gAMAwEAAhADEAAAAZ6wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/8QAFBABAAAAAAAAAAAAAAAAAAAAYP/aAAgBAQABBQIB/8QAFBEBAAAAAAAAAAAAAAAAAAAAYP/aAAgBAwEBPwEB/8QAFBEBAAAAAAAAAAAAAAAAAAAAYP/aAAgBAgEBPwEB/8QAFBABAAAAAAAAAAAAAAAAAAAAYP/aAAgBAQAGPwIB/8QAFBABAAAAAAAAAAAAAAAAAAAAYP/aAAgBAQABPyEB/9oADAMBAAIAAwAAABDbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/xAAUEQEAAAAAAAAAAAAAAAAAAABg/9oACAEDAQE/EAH/xAAUEQEAAAAAAAAAAAAAAAAAAABg/9oACAECAQE/EAH/xAAUEAEAAAAAAAAAAAAAAAAAAABg/9oACAEBAAE/EAH/2Q==`;

let db;
// init example db instance
function initDb() {
  db = new PouchDB('test', {
    adapter: 'memory'
  });

  let doc = {
    _id: "Purple Box",
    "_attachments": {
      "thumbnail": {
        "content_type": "image/jpeg;base64",
        "data": jpegData
      }
    }
  }
  return db.put(doc);
};

var app = angular.module("pouchy", []);
app.controller("pouchyAttachment", async($scope) => {
  // setup the test database
  await initDb();  
  let attachment = await db.getAttachment("Purple Box", "thumbnail");
  $scope.attachment = attachment;
  let reader = new FileReader();
  // setup image after attachment blob is loaded
  reader.addEventListener("loadend", () => {
    $scope.image = reader.result;
    // this is outside the AngularJS, must use $scope.apply() to get update.
    $scope.$apply();
    // clean up don't leak stuff
    reader.removeEventListener("loadend", this);
  });
  reader.readAsDataURL(attachment);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb-7.1.1.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.memory.min.js"></script>

<div ng-app="pouchy" ng-controller="pouchyAttachment">    
  <div>Type: {{attachment.type}}</div>
  <div>Size: {{attachment.size}}</div>
  <img id="pouchyImage" src="{{image}}" />
</div>

Update

If you are in fact using Angular as opposed to AngularJS, you need to jump through a few hoops.

As an example, setup a property binding like so

<img [src]="image" />

For a component, add DomSanitizer and NgZone

import { NgZone, Component } from "@angular/core";
import { DomSanitizer } from "@angular/platform-browser";
/*etc*/
constructor(private sanitizer: DomSanitizer, private zone: NgZone, /*etc*/) { 
 /*etc*/
}

The DomSanitizer is used so the image data is otherwise marked as safe; NgZone so the property change to the src binding is detected and processed.

So something like this will do the trick

    // some method body
    let attachment: Blob = (await db.getAttachment(docId, attachmentName")) as Blob;
    const reader = new FileReader();
    const _zone = this.zone;
    const handler = () => {
       _zone.run(() => {
          this.image = this.sanitizer.bypassSecurityTrustUrl(reader.result as string);
          reader.removeEventListener("loadend", handler);
       });
    };
    // setup image after attachment blob is loaded
    reader.addEventListener("loadend", handler);
    reader.readAsDataURL(attachment as any);
RamblinRose
  • 4,883
  • 2
  • 21
  • 33