Photo gallery tutorial: Photo not being saved to gallery grid

Hi,
Searching for a related question was not showing a result, so asking here. I am trying the tutorial for the photo gallery app but when I click the (click)="addPhotoToGallery()" I get this in the console without the photo being added to the grid:

GET http://localhost:8100/undefined 404 (Not Found)

tab2.page.html:

<ion-header>
  <ion-toolbar>
    <ion-title>
      Photo Gallery
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Photo Gallery</ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-grid>
    <ion-row>
      <ion-col size="6" *ngFor="let photo of photos; index as position">
        <ion-img [src]="photo.webviewPath"></ion-img>
      </ion-col>
    </ion-row>
  </ion-grid>

  <ion-fab vertical="bottom" horizontal="center" slot="fixed">
    <ion-fab-button (click)="addPhotoToGallery()">
      <ion-icon name="camera"></ion-icon>
    </ion-fab-button>
  </ion-fab>

</ion-content>

photo.service.ts:

import { Injectable } from '@angular/core';
import {
  Plugins, CameraResultType, Capacitor, FilesystemDirectory,
  CameraPhoto, CameraSource
} from '@capacitor/core';

const { Camera, Filesystem, Storage } = Plugins;

@Injectable({
  providedIn: 'root'
})
export class PhotoService {
  public photos: Photo[] = [];

  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
    });
  }

}

export interface Photo {
  filepath: string;
  webviewPath: string;
}

This is on a windows system. Camera opens and I capture a pic, with the green check mark to save it. After the the green check mark, we then see it is added to the array.

Then after selecting the green save after the capture we get the filled array of photos:

The rest of the files are to the T from the tutorial. Any ideas?

Thanks

I fixed it finally. In tab2.page.ts I needed to declare another variable of type Photo[] and assign as such:

export class Tab2Page {
  public photos: Photo[] = [];

  constructor(public photoService: PhotoService) { }

  async addPhotoToGallery() {
    await this.photoService.addNewToGallery();
    this.photos = this.photoService.photos;
  }
}

otherwise, the *ngFor loop in the template does not get its photos array updated.

I would consider the post above this one (currently marked as the “solution”) to be backwards.

It’s brittle, because it is very easy to end up with the exact same problem in anywhere else that interacts with PhotoService. The problem, IMHO, is the fact that PhotoService.photos is public. Propagating changes to shared data is arguably the entire reason that ReactiveX exists, so I would highly recommend using it here.

Instead of exposing photos, I would do something like this:

class PhotoService {
  photos$ = new BehaviorSubject<Photo[]>([]);

  peekPhotos(): Photo[] {
    return this.photos$.value;
  }

  watchPhotos(): Observable<Photo[]> {
    return this.photos$;
  }

  addNewToGallery(): void {
    let photo = this.existingAddNewToGallery();
    this.photos$.next(photo);
  }
}

By decoupling the communication of updates from the process of making an update, we smoothly allow consumers other than the one who made the update to receive updates in a timely fashion, and we eliminate a potential source of bugs.

Thanks! I will look into it. I just wonder why the tutorial was set up the way it is though?

I do not understand how this is supposed to work? This:

let photo = this.existingAddNewToGallery();

is a ZoneAwarePromise whereas:

this.photos$.next(photo);

is supposed to take a Photo[]

Can you please elaborate on your idea?

Thanks

I did a variation of your solution that worked:

export class PhotoService {
  public photos: Photo[] = [];

  private photos$ = new BehaviorSubject<Photo[]>([]);

  getPhotos(): Observable<Photo[]> {
    return this.photos$.asObservable();
  }

  setPhotos(photos: Photo[]) {
    this.photos$.next(photos);
}
  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
    });

    this.setPhotos(this.photos);
  }
}

Then in:

export class Tab2Page {
  public photos: Photo[] = [];

  constructor(public photoService: PhotoService) { }

  addPhotoToGallery() {
    this.photoService.addNewToGallery();
    this.photoService.getPhotos().subscribe(photos => this.photos = photos);
  }
}

That was just shorthand for “do whatever addNewToGallery did in your original”.

Great. There’s no need for asObservable, you can just return this.photos$ out of getPhotos.

1 Like