Launch sale! Get 30% OFF expires in 126:46:22

Existing member? Log in and continue learning

See if you like it, start the course for free!

Unlock full course by purchasing a membership
Taking Photos with the Camera API
Taking Photos with the Camera API
For the last application we built, we first considered which feature to build first. Usually, I like to focus on the key functionality of the application first if possible. In this case, we are building an application centered around taking photos, so let’s implement that first.
Create a Photo Interface
export interface PhotoData { name: string; path: string; dateTaken: string;}
For our Photo
entity we are interested in keep tracking of a name
which will
be used as a file name for that photo, the path to where it is stored on the
device, and the date it was take which we will display to the user.
Create a Photo Service
import { Injectable, signal } from '@angular/core';import { PhotoData } from 'src/app/shared/interfaces/photo';
@Injectable({ providedIn: 'root',})export class PhotoService { photos = signal<PhotoData[]>([]);}
Pretty simple for now, we just have a photos
signal to hold our state.
Create a source for adding photos
Now we are going to add an add$
source for adding new photos to our state. In
many ways we are going to be doing more or less the same general thing we did in
the Quicklists application: we will have a source
, we create a reducer
for
that source
, and that reducer
will update the state in some way.
This application will be a little more complex though, as when we trigger our
add$
source we will need to trigger asynchronous operators, rather than just
having the data we need to add available synchronously immediately.
export class PhotoService { // sources add$ = new Subject<void>();
// state photos = signal<PhotoData[]>([]);
constructor() { this.add$ .pipe(takeUntilDestroyed()) .subscribe(() => this.photos.update((photos) => [...photos])); }}
We can see the same basic redux plumbing here: source > reducer > state
. The
problem here though is that our add$
source has a type of void
. It doesn’t
receive any data, it just gets triggered. All our reducer is doing is responding
to this nothingness and updating our photos
with no new data. So, at the
moment, what we are doing is pointless.
What we need to do now is trigger Capacitor’s Camera API when the add$
source
is triggered. This is a Promise based API that will return us some photo data
when the Promise resolves. We will then need to take that data and update our
state with it.
First, we are going to focus on setting up the configuration we need for the
Camera API. But take some time to think about how we might end up changing our
add$
reducer in the constructor to integrate this asynchronous request to the
Camera API (hint: we will be doing it entirely with observables/operators within
the pipe
of the add$
reducer).
Setting up for the Camera API
npm install @capacitor/camera
Let’s get all the pieces in place first and then we can talk through how it works.
import { inject, Injectable, signal } from '@angular/core';import { takeUntilDestroyed } from '@angular/core/rxjs-interop';import { Camera, CameraResultType, CameraSource, ImageOptions,} from '@capacitor/camera';import { Platform } from '@ionic/angular';import { EMPTY, Subject } from 'rxjs';import { switchMap, catchError } from 'rxjs/operators';import { PhotoData } from 'src/app/shared/interfaces/photo';
@Injectable({ providedIn: 'root',})export class PhotoService { platform = inject(Platform);
options: ImageOptions = { quality: 50, width: 600, allowEditing: false, resultType: this.platform.is('capacitor') ? CameraResultType.Uri : CameraResultType.DataUrl, source: CameraSource.Camera, };
// sources add$ = new Subject<void>();
// state photos = signal<PhotoData[]>([]);
constructor() { // reducers this.add$ .pipe( switchMap(() => Camera.getPhoto(this.options)), catchError(() => EMPTY), takeUntilDestroyed(), ) .subscribe((photo) => this.photos.update((photos) => [ ...photos, { name: Date.now().toString(), path: photo.path ?? photo.dataUrl, dateTaken: new Date().toISOString(), } as PhotoData, ]), ); }}
We will talk about Camera
itself in just a moment, but let’s first just notice
the general structure here. For our reducer we still just have a direct call to
update
the photos signal in the subscribe
but now we are supplying it with
actual data due to the changes we made to the pipe
.
We use the switchMap
operator to call Camera.getPhoto
every time our add$
source is triggered. The getPhoto
call will return us a Promise
which is
automatically converted into an observable for us by switchMap
. The data from
getPhoto
will then be passed to our subscribe
when it resolves.
We have also added a catchError
in case the getPhoto
errors. This is just
basic error handling that won’t actually do anything, but it will prevent our
stream from breaking entirely by just switching to an EMPTY
stream if
getPhoto
errors. This means that the next time add$
is triggered it will
still work, even if one of the previous calls erroed catchError
in case the
getPhoto
errors. This is just basic error handling that won’t actually do
anything, but it will prevent our stream from breaking entirely by just
switching to an EMPTY
stream if getPhoto
errors. This means that the next
time add$
is triggered it will still work, even if one of the previous calls
errored.
The call to the Camera plugin itself is reasonably straightforward:
Camera.getPhoto(this.options);
But it’s the options for taking the photo that are a little more
interesting. Some of these options are just general configurations like the
quality of the image, whether editing should be allowed, whether to take a new
photo with the Camera
or to use the existing Photos
in the users gallery,
the size, and there are other options available too.
But then we have this:
Thanks for checking out the preview of this lesson!
You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).