Saving Photos to the Filesystem
We’re now able to take multiple photos and display them in a photo gallery on the second tab of our app. These photos, however, are not currently being stored permanently, so when the app is closed, they will be deleted.
Filesystem API
Fortunately, saving them to the filesystem only takes a few steps. Begin by creating a new class method, savePicture()
, in the PhotoService
class (src/app/services/photo.service.ts
). We pass in the photo
object, which represents the newly captured device photo:
import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
@Injectable({
providedIn: 'root'
})
export class PhotoService {
public photos: UserPhoto[] = [];
constructor() { }
public async addNewToGallery() {
// Take a photo
const capturedPhoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 100,
});
this.photos.unshift({
filepath: "soon...",
webviewPath: capturedPhoto.webPath!
});
}
// CHANGE: Add the `savePicture` method.
private async savePicture(photo: Photo) { }
}
export interface UserPhoto {
filepath: string;
webviewPath?: string;
}
We can use this new method immediately in addNewToGallery()
:
import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
@Injectable({
providedIn: 'root'
})
export class PhotoService {
public photos: UserPhoto[] = [];
constructor() { }
public async addNewToGallery() {
// Take a photo
const capturedPhoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 100,
});
// CHANGE: Add `savedImageFile`
// Save the picture and add it to photo collection
const savedImageFile = await this.savePicture(capturedPhoto);
// CHANGE: Update argument to unshift array method
this.photos.unshift(savedImageFile);
}
private async savePicture(photo: Photo) { }
}
export interface UserPhoto {
filepath: string;
webviewPath?: string;
}
We’ll use the Capacitor Filesystem API to save the photo to the filesystem. To start, convert the photo to base64 format, then feed the data to the Filesystem’s writeFile
function. As you’ll recall, we display each photo on the screen by setting each image’s source path (src
attribute) in tab2.page.html
to the webviewPath property. So, set it then return the new Photo object.
import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
@Injectable({
providedIn: 'root'
})
export class PhotoService {
public photos: UserPhoto[] = [];
constructor() { }
public async addNewToGallery() {
// Take a photo
const capturedPhoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 100,
});
// Save the picture and add it to photo collection
const savedImageFile = await this.savePicture(capturedPhoto);
this.photos.unshift(savedImageFile);
}
// CHANGE: Update the `savePicture` method.
private async savePicture(photo: Photo) {
// Convert photo to base64 format, required by Filesystem API to save
const base64Data = await this.readAsBase64(photo);
// Write the file to the data directory
const fileName = Date.now() + '.jpeg';
const savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data
});
// Use webPath to display the new image instead of base64 since it's
// already loaded into memory
return {
filepath: fileName,
webviewPath: photo.webPath
};
}
}
export interface UserPhoto {
filepath: string;
webviewPath?: string;
}
readAsBase64()
is a helper function we’ll define next. It's useful to organize via a separate method since it requires a small amount of platform-specific (web vs. mobile) logic - more on that in a bit. For now, we'll create two new helper functions, readAsBase64()
and convertBlobToBase64()
, to implement the logic for running on the web:
import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
@Injectable({
providedIn: 'root'
})
export class PhotoService {
public photos: UserPhoto[] = [];
constructor() { }
public async addNewToGallery() {
// Take a photo
const capturedPhoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 100,
});
// Save the picture and add it to photo collection
const savedImageFile = await this.savePicture(capturedPhoto);
this.photos.unshift(savedImageFile);
}
private async savePicture(photo: Photo) {
// Convert photo to base64 format, required by Filesystem API to save
const base64Data = await this.readAsBase64(photo);
// Write the file to the data directory
const fileName = Date.now() + '.jpeg';
const savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data
});
// Use webPath to display the new image instead of base64 since it's
// already loaded into memory
return {
filepath: fileName,
webviewPath: photo.webPath
};
}
// CHANGE: Add the `readAsBase64` method.
private async readAsBase64(photo: Photo) {
// Fetch the photo, read as a blob, then convert to base64 format
const response = await fetch(photo.webPath!);
const blob = await response.blob();
return await this.convertBlobToBase64(blob) as string;
}
// CHANGE: Add the `convertBlobToBase64` method.
private convertBlobToBase64 = (blob: Blob) => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onerror = reject;
reader.onload = () => {
resolve(reader.result);
};
reader.readAsDataURL(blob);
});
}
export interface UserPhoto {
filepath: string;
webviewPath?: string;
}
photo.service.ts
should now look like this:
import { Injectable } from '@angular/core';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
@Injectable({
providedIn: 'root',
})
export class PhotoService {
public photos: UserPhoto[] = [];
constructor() {}
public async addNewToGallery() {
const capturedPhoto = await Camera.getPhoto({
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
quality: 100,
});
// Save the picture and add it to photo collection
const savedImageFile = await this.savePicture(capturedPhoto);
// update argument to unshift array method
this.photos.unshift(savedImageFile);
}
private async savePicture(photo: Photo) {
// Convert photo to base64 format, required by Filesystem API to save
const base64Data = await this.readAsBase64(photo);
// Write the file to the data directory
const fileName = Date.now() + '.jpeg';
const savedFile = await Filesystem.writeFile({
path: fileName,
data: base64Data,
directory: Directory.Data,
});
// Use webPath to display the new image instead of base64 since it's
// already loaded into memory
return {
filepath: fileName,
webviewPath: photo.webPath,
};
}
private async readAsBase64(photo: Photo) {
// Fetch the photo, read as a blob, then convert to base64 format
const response = await fetch(photo.webPath!);
const blob = await response.blob();
return (await this.convertBlobToBase64(blob)) as string;
}
private convertBlobToBase64 = (blob: Blob) =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onerror = reject;
reader.onload = () => {
resolve(reader.result);
};
reader.readAsDataURL(blob);
});
}
export interface UserPhoto {
filepath: string;
webviewPath?: string;
}
Obtaining the camera photo as base64 format on the web appears to be a bit trickier than on mobile. In reality, we’re just using built-in web APIs: fetch() as a neat way to read the file into blob format, then FileReader’s readAsDataURL() to convert the photo blob to base64.
There we go! Each time a new photo is taken, it’s now automatically saved to the filesystem.
Next up, we'll load and display our saved images. when the user navigates to the Photos tab.