In my previous post you have already seen how to setup Ionic 2 application and take photos with a native camera. In this post, I show you how to add a local storage persistence layer for your photos with Ionic 2 framework.

This post is part of a blog post series. Check out the other parts here:

Part 1: Setup Ionic 2 Application and Take Photos with a Native Camera

Part 2: Add a Local Storage Persistence Layer to your Photos (this post!) 

Part 3: Use Location Plugins to Add Coordinates for Every Photo Taken and Place Your Images on Google Maps

Part 4: Change the Persistence Layer with Firebase, and Make a Real-time Application

Add a Local Storage Persistence Layer to Your Photos

Localforage is a simple storage with a LocalStorage-like API, and it supports storage technologies such as WebSQL, IndexedDB and LocalStorage.

Temporarily, I will use this storage to persist the taken pictures.

While it’s just base64 data right now, we can store the photos in a string array, where every string will be a base64 representation of the picture.

You will need 2 packages:

  • The storage itself
  • The typing for this package (if you’re using Typescript)
npm i -S localforage
npm i -D @types/localforage

If it’s done, then we have to make a service to wrap the storage actions. Let’s call it PhotoStorage.

It’s very straightforward. If you need, you can create more independent storages with different configurations to persist data. You can check the configuration steps here.

Html5 Img

This is our service class that can be called if we need to invoke get, save and delete actions. It doesn’t have any angular dependencies, as you can see. There are no parameters in the constructor. It contains only minimal and very straightforward callable functions.

import { Injectable } from '@angular/core';
import * as localforage from 'localforage';
import { attempt, isError } from 'lodash';

const PHOTO_COLLECTION = 'photos';

export interface PhotoRecord {
  data: string;
}

@Injectable()
export class PhotoStorage {
  constructor() {}

  getPhotos(): Promise<PhotoRecord[]> {
    return localforage.getItem(PHOTO_COLLECTION).then(photoList => {
      if (!photoList) {
        return [];
      }
      const parsedPhotoList = attempt(() => JSON.parse(photoList as string)) as PhotoRecord[];
      if (isError(parsedPhotoList)) {
        throw parsedPhotoList;
      }
 return parsedPhotoList;
    });
  }

  addPhoto(photoData: string) {
    return this.getPhotos().then(photoList => {
      photoList.push({ data: photoData });
      return localforage.setItem(PHOTO_COLLECTION, JSON.stringify(photoList));
    });
  }

  deletePhoto(index: number) {
    return this.getPhotos().then(photoList => {
      if (photoList && photoList.length) {
        photoList.splice(index, 1);
        return localforage.setItem(PHOTO_COLLECTION, JSON.stringify(photoList));
      }
      return null;
    });
  }
}

In the PhotoPage’s constructor, which is extended with the new service, we can load the already saved photo list. The getPhotos() function will give back a promise with the entire photo array. We can access this value in the "then" statement.

private photoList: PhotoRecord[] = [];

constructor(
    private photoStorage: PhotoStorage,
    private navCtrl: NavController,
    private DomSanitizer: DomSanitizer
) {
    this.photoStorage.getPhotos().then(photos => {
      this.photoList = photos;
    })
}

Using promises is a good way to avoid callback hells.

Callbacks

Besides, we have two new functions: one to add a new photo to LocalStorage, and one to remove from that by its index.

You can see that since after each action, we will reload the complete list from the storage.

<button ion-button icon-left clear item-left (click)="savePicture()">
  <ion-icon name="cloud-upload"></ion-icon> Save
</button>

savePicture() {
  if (this.base64Image !== this.PLACEHOLDER) {
    return this.photoStorage.addPhoto(this.base64Image).then(() => {
      return this.photoStorage.getPhotos()
        .then(photoList => this.photoList = photoList);
    });
  }
  return Promise.resolve();
}

Let’s see the photo remove action.

We need an index for the selected image to find it in the array. This property comes from the ngFor directive’s index value, which will be different for every row. We just need this value because we don’t have any unique identifier for our records. This photoList array is coming from the component that holds the base64 strings for every photo.

<ion-card *ngFor="let photo of photoList; let ind = index">
  <div class="photo-image-wrapper">
    <img [src]="DomSanitizer.bypassSecurityTrustUrl(photo.data)"/>
  </div>
  <ion-item>
    <button ion-button color="danger" icon-left clear item-right (click)="deletePicture(ind)">
      <ion-icon name="trash"></ion-icon>
      Delete
    </button>
  </ion-item>
</ion-card>

deletePicture(index) {
  return this.photoStorage.deletePhoto(index).then(() => {
    return this.photoStorage.getPhotos()
      .then(photoList => this.photoList = photoList);
  });
}

These simple steps make our application able to store the taken images in a temporary storage. This storing procedure is not a production-ready feature because it only keeps the data until the app is reinstalled or the application cache is cleared.

The entire data change is a read-modify-write flow, which has some performance issues, and we also cannot access individual records. But it’s good enough for now.

From part 3, you will learn how to use location plugins to add coordinates to every photo taken and place your images on Google Maps.


State of Software Develoopment Report