From 9c0859d030f71a00ec61f885ca61f8276423b78b Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Fri, 13 Jun 2025 13:14:36 -0500 Subject: [PATCH 01/38] docs(angular): add context to code blocks in your-first-app page --- docs/angular/your-first-app.md | 61 +++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 739a9c3d29b..99a61d9ead3 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -110,10 +110,15 @@ npm install @ionic/pwa-elements Next, import `@ionic/pwa-elements` by editing `src/main.ts`. ```tsx -import { defineCustomElements } from '@ionic/pwa-elements/loader'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; +import { defineCustomElements } from '@ionic/pwa-elements/loader'; // Added import // Call the element loader before the bootstrapModule/bootstrapApplication call defineCustomElements(window); + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.log(err)); ``` That’s it! Now for the fun part - let’s see the app in action. @@ -137,18 +142,22 @@ There are three tabs. Click on the Tab2 tab. It’s a blank canvas, aka the perf Open the photo-gallery app folder in your code editor of choice, then navigate to `/src/app/tab2/tab2.page.html`. We see: ```html - + - Tab 2 + + Tab 2 + - + Tab 2 + + ``` @@ -161,22 +170,58 @@ Open the photo-gallery app folder in your code editor of choice, then navigate t We put the visual aspects of our app into ``. In this case, it’s where we’ll add a button that opens the device’s camera as well as displays the image captured by the camera. Start by adding a [floating action button](https://ionicframework.com/docs/api/fab) (FAB) to the bottom of the page and set the camera image as the icon. ```html + + + + Tab 2 + + + + + + + Tab 2 + + + + + + + ``` Next, open `src/app/tabs/tabs.page.html`. Change the label to “Photos” and the icon name to “images”: ```html - - - Photos - + + + + + + Tab 1 + + + + + + + Photos + + + + + Tab 3 + + + + ``` Save all changes to see them automatically applied in the browser. That’s just the start of all the cool things we can do with Ionic. Up next, implement camera taking functionality on the web, then build it for iOS and Android. From e979ea8fe623a73572876eeba7bc54a8e136cfa8 Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Fri, 13 Jun 2025 14:03:09 -0500 Subject: [PATCH 02/38] docs(angular): add context to code blocks and note to learn more about ngFor in 2-taking-photos.md --- .../angular/your-first-app/2-taking-photos.md | 130 +++++++++++++++++- 1 file changed, 123 insertions(+), 7 deletions(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 0e0cd078a70..2cb41f30321 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -44,27 +44,81 @@ public async addNewToGallery() { Notice the magic here: there's no platform-specific code (web, iOS, or Android)! The Capacitor Camera plugin abstracts that away for us, leaving just one method call - `Camera.getPhoto()` - that will open up the device's camera and allow us to take photos. +Your updated `photo.service.ts` should now look like this: + +```tsx +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 { + + constructor() { } + + public async addNewToGallery() { + // Take a photo + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + quality: 100 + }); + } +} +``` + Next, open up `tab2.page.ts` and import the PhotoService class and add a method that calls the `addNewToGallery` method on the imported service: ```tsx +import { Component } from '@angular/core'; import { PhotoService } from '../services/photo.service'; -constructor(public photoService: PhotoService) { } - -addPhotoToGallery() { - this.photoService.addNewToGallery(); +@Component({ + selector: 'app-tab2', + templateUrl: 'tab2.page.html', + styleUrls: ['tab2.page.scss'], + standalone: false, +}) +export class Tab2Page { + + // update constructor to include photoService + constructor(public photoService: PhotoService) { } + + // add addNewToGallery method + addPhotoToGallery() { + this.photoService.addNewToGallery(); + } } ``` Then, open `tab2.page.html` and call the `addPhotoToGallery()` function when the FAB is tapped/clicked: ```html + + + + Tab 2 + + + + + + + Tab 2 + + + + + ``` @@ -78,7 +132,7 @@ After taking a photo, it disappears right away. We need to display it within our ## Displaying Photos -Outside of the `PhotoService` class definition (the very bottom of the file), create a new interface, `UserPhoto`, to hold our photo metadata: +Return to `photo.service.ts`. Outside of the `PhotoService` class definition (the very bottom of the file), create a new interface, `UserPhoto`, to hold our photo metadata: ```tsx export interface UserPhoto { @@ -87,12 +141,14 @@ export interface UserPhoto { } ``` -Back at the top of the file, define an array of Photos, which will contain a reference to each photo captured with the Camera. +Back at the top of the `PhotoService` class definition, define an array of Photos, which will contain a reference to each photo captured with the Camera. ```tsx export class PhotoService { public photos: UserPhoto[] = []; + constructor() { } + // other code } ``` @@ -100,34 +156,94 @@ export class PhotoService { Over in the `addNewToGallery` function, add the newly captured photo to the beginning of the Photos array. ```tsx +public async addNewToGallery() { const capturedPhoto = await Camera.getPhoto({ resultType: CameraResultType.Uri, source: CameraSource.Camera, quality: 100 }); + // add new photo to photos array this.photos.unshift({ filepath: "soon...", webviewPath: capturedPhoto.webPath! }); } ``` +`photo.service.ts` should now look like this: + +```tsx +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 + }); + + // add new photo to photos array + this.photos.unshift({ + filepath: "soon...", + webviewPath: capturedPhoto.webPath! + }); + } +} +export interface UserPhoto { + filepath: string; + webviewPath?: string; +} +``` Next, move over to `tab2.page.html` so we can display the image on the screen. Add a [Grid component](https://ionicframework.com/docs/api/grid) so that each photo will display nicely as photos are added to the gallery, and loop through each photo in the `PhotoServices`'s Photos array, adding an Image component (``) for each. Point the `src` (source) at the photo’s path: ```html + + + + Tab 2 + + + + + + + Tab 2 + + + + + - + + + + + + ``` +:::note +Learn more about the [ngFor core directive](https://blog.angular-university.io/angular-2-ngfor/). +::: Save all files. Within the web browser, click the Camera button and take another photo. This time, the photo is displayed in the Photo Gallery! From 67d1164284334b85f2ec3e38546068a842dce796 Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Fri, 13 Jun 2025 14:27:31 -0500 Subject: [PATCH 03/38] docs(angular): add context to method definitions, additional code block to 3-saving-photos.md --- .../angular/your-first-app/3-saving-photos.md | 77 ++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index c1e013a8bec..a162a608a55 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -27,6 +27,7 @@ public async addNewToGallery() { // 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); } ``` @@ -55,7 +56,7 @@ private async savePicture(photo: Photo) { } ``` -`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, implement the logic for running on the web: +`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: ```tsx private async readAsBase64(photo: Photo) { @@ -76,6 +77,78 @@ private convertBlobToBase64 = (blob: Blob) => new Promise((resolve, reject) => { }); ``` +`photo.service.ts` should now look like this: + +```tsx +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()](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) as a neat way to read the file into blob format, then FileReader’s [readAsDataURL()](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/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. +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. \ No newline at end of file From ebb2a94727279e8610af2ef5f732649fc6485b0b Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Mon, 16 Jun 2025 10:35:35 -0500 Subject: [PATCH 04/38] docs(angular): add context in code blocks, clarity, storage note in 4-loading-photos.md --- .../your-first-app/4-loading-photos.md | 162 ++++++++++++++++-- 1 file changed, 150 insertions(+), 12 deletions(-) diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index 6f59d3f951f..e87abec44cb 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -10,27 +10,44 @@ Fortunately, this is easy: we’ll leverage the Capacitor [Preferences API](http ## Preferences API -Begin by defining a constant variable that will act as the key for the store: +Open `photo.service.ts` and begin by defining a new property in the `PhotoService` class that will act as the key for the store: ```tsx export class PhotoService { public photos: UserPhoto[] = []; + + // add key for photo store private PHOTO_STORAGE: string = 'photos'; - // other code + constructor() {} + + // other code... } ``` Next, at the end of the `addNewToGallery` function, add a call to `Preferences.set()` to save the Photos array. By adding it here, the Photos array is stored each time a new photo is taken. This way, it doesn’t matter when the app user closes or switches to a different app - all photo data is saved. ```tsx -Preferences.set({ - key: this.PHOTO_STORAGE, - value: JSON.stringify(this.photos), -}); +public async addNewToGallery() { + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + quality: 100, + }); + + const savedImageFile = await this.savePicture(capturedPhoto); + + this.photos.unshift(savedImageFile); + + // Add call to set() method to cache all photo data for future retrieval + Preferences.set({ + key: this.PHOTO_STORAGE, + value: JSON.stringify(this.photos), + }); +} ``` -With the photo array data saved, create a function called `loadSaved()` that can retrieve that data. We use the same key to retrieve the photos array in JSON format, then parse it into an array: +With the photo array data saved, create a new public method in the `PhotoService` class called `loadSaved()` that can retrieve the photo data. We use the same key to retrieve the photos array in JSON format, then parse it into an array: ```tsx public async loadSaved() { @@ -42,7 +59,7 @@ public async loadSaved() { } ``` -On mobile (coming up next!), we can directly set the source of an image tag - `` - to each photo file on the Filesystem, displaying them automatically. On the web, however, we must read each image from the Filesystem into base64 format, using a new `base64` property on the `Photo` object. This is because the Filesystem API uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood. Below is the code you need to add in the `loadSaved()` function you just added: +On mobile (coming up next!), we can directly set the source of an image tag - `` - to each photo file on the Filesystem, displaying them automatically. On the web, however, we must read each image from the Filesystem into base64 format, using a new `base64` property on the `Photo` object. This is because the Filesystem API uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood. Add the following code to complete the `loadSaved()` function: ```tsx // Display the photo by reading into base64 format @@ -57,13 +74,134 @@ for (let photo of this.photos) { photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; } ``` - -After, call this new method in `tab2.page.ts` so that when the user first navigates to Tab 2 (the Photo Gallery), all photos are loaded and displayed on the screen. +After these updates to the `PhotoService` class, your `photos.service.ts` file should look like this: ```tsx -async ngOnInit() { - await this.photoService.loadSaved(); +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[] = []; + private PHOTO_STORAGE = 'photos'; + + constructor() {} + + public async addNewToGallery() { + // Take a photo + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, // file-based data; provides best performance + source: CameraSource.Camera, // automatically take a new photo with the camera + quality: 100, // highest quality (0 to 100) + }); + + const savedImageFile = await this.savePicture(capturedPhoto); + + // Add new photo to Photos array + this.photos.unshift(savedImageFile); + + // Cache all photo data for future retrieval + Preferences.set({ + key: this.PHOTO_STORAGE, + value: JSON.stringify(this.photos), + }); + } + + public async loadSaved() { + // Retrieve cached photo array data + const { value } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (value ? JSON.parse(value) : []) as UserPhoto[]; + + // Display the photo by reading into base64 format + for (let photo of this.photos) { + // Read each saved photo's data from the Filesystem + const readFile = await Filesystem.readFile({ + path: photo.filepath, + directory: Directory.Data, + }); + + // Web platform only: Load the photo as base64 data + photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; + } + } + + 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; } ``` +Our `PhotoService` can now load the saved images, but we'll need to update `tab2.page.ts` to put that new code to work. We'll call `loadSaved` within the [ngOnInit](https://angular.dev/guide/components/lifecycle#ngoninit) lifecycle method so that when the user first navigates to Tab 2 (the Photo Gallery), all photos are loaded and displayed on the screen. Update `tab2.page.ts` to look like the following: + +```tsx +import { Component } from '@angular/core'; +import { PhotoService } from '../services/photo.service'; + +@Component({ + selector: 'app-tab2', + templateUrl: 'tab2.page.html', + styleUrls: ['tab2.page.scss'], + standalone: false, +}) +export class Tab2Page { + + constructor(public photoService: PhotoService) {} + + // add call to loadSaved on navigation to Photos tab + async ngOnInit() { + await this.photoService.loadSaved(); + } + + addPhotoToGallery() { + this.photoService.addNewToGallery(); + } +} +``` +:::note +If you're seeing broken image links or missing photos after following these steps, you may need to open your browser's +dev tools and clear both [localStorage](https://developer.chrome.com/docs/devtools/storage/localstorage) and [IndexedDB](https://developer.chrome.com/docs/devtools/storage/indexeddb). +::: That’s it! We’ve built a complete Photo Gallery feature in our Ionic app that works on the web. Next up, we’ll transform it into a mobile app for iOS and Android! From 2660f4c544823d061d65fee1ec6f778402649700 Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Mon, 16 Jun 2025 11:13:10 -0500 Subject: [PATCH 05/38] docs(angular): add context and clarity to code blocks in 5-adding-mobile.md --- .../angular/your-first-app/5-adding-mobile.md | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index ce7f60e75c5..4de0c0d0eb4 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -10,27 +10,38 @@ Our photo gallery app won’t be complete until it runs on iOS, Android, and the Let’s start with making some small code changes - then our app will “just work” when we deploy it to a device. -Import the Ionic [Platform API](https://ionicframework.com/docs/angular/platform) into `photo.service.ts`, which is used to retrieve information about the current device. In this case, it’s useful for selecting which code to execute based on the platform the app is running on (web or mobile): +Import the Ionic [Platform API](https://ionicframework.com/docs/angular/platform) into `photo.service.ts`, which is used to retrieve information about the current device. In this case, it’s useful for selecting which code to execute based on the platform the app is running on (web or mobile). + +Add `Platform` to the imports at the top of the file and a new property `platform` to the `PhotoService` class. We'll also need to update the constructor to set the user's platform: ```tsx +import { Injectable } from '@angular/core'; +import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Preferences } from '@capacitor/preferences'; + +// add Platform import import { Platform } from '@ionic/angular'; export class PhotoService { public photos: UserPhoto[] = []; private PHOTO_STORAGE: string = 'photos'; + + // add property platform to store which platform app is running on private platform: Platform; + // update constructor to set platform property constructor(platform: Platform) { this.platform = platform; } - // other code + // other code... } ``` ## Platform-specific Logic -First, we’ll update the photo saving functionality to support mobile. In the `readAsBase64()` function, check which platform the app is running on. If it’s “hybrid” (Capacitor or Cordova, two native runtimes), then read the photo file into base64 format using the Filesystem `readFile()` method. Otherwise, use the same logic as before when running the app on the web: +First, we’ll update the photo saving functionality to support mobile. In the `readAsBase64()` function, check which platform the app is running on. If it’s “hybrid” (Capacitor or Cordova, two native runtimes), then read the photo file into base64 format using the Filesystem `readFile()` method. Otherwise, use the same logic as before when running the app on the web. Update `readAsBase64()` to look like the following: ```tsx private async readAsBase64(photo: Photo) { @@ -53,8 +64,13 @@ private async readAsBase64(photo: Photo) { } ``` -Next, update the `savePicture()` method. When running on mobile, set `filepath` to the result of the `writeFile()` operation - `savedFile.uri`. When setting the `webviewPath`, use the special `Capacitor.convertFileSrc()` method ([details here](https://ionicframework.com/docs/core-concepts/webview#file-protocol)). +Next, update the `savePicture()` method. When running on mobile, set `filepath` to the result of the `writeFile()` operation - `savedFile.uri`. When setting the `webviewPath`, use the special `Capacitor.convertFileSrc()` method ([details on the File Protocol](https://ionicframework.com/docs/core-concepts/webview#file-protocol)). To use this method, we'll need to import Capacitor at the +top of `photo.service.ts`. +```tsx +import { Capacitor } from '@capacitor/core'; +``` +Then update `savePicture()` to look like the following: ```tsx // Save picture to file on device private async savePicture(photo: Photo) { From 08cf534ad46d42eb478cd65016a9605cb6544332 Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Mon, 16 Jun 2025 12:37:34 -0500 Subject: [PATCH 06/38] docs(angular): ran linter to format changes to angular walk through --- docs/angular/your-first-app.md | 15 +++----- .../angular/your-first-app/2-taking-photos.md | 35 ++++++++----------- .../angular/your-first-app/3-saving-photos.md | 30 ++++++++-------- .../your-first-app/4-loading-photos.md | 27 +++++++------- .../angular/your-first-app/5-adding-mobile.md | 2 ++ 5 files changed, 52 insertions(+), 57 deletions(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 99a61d9ead3..0f14b8a0e2e 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -117,8 +117,9 @@ import { defineCustomElements } from '@ionic/pwa-elements/loader'; // Added impo // Call the element loader before the bootstrapModule/bootstrapApplication call defineCustomElements(window); -platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.log(err)); +platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch((err) => console.log(err)); ``` That’s it! Now for the fun part - let’s see the app in action. @@ -144,9 +145,7 @@ Open the photo-gallery app folder in your code editor of choice, then navigate t ```html - - Tab 2 - + Tab 2 @@ -172,9 +171,7 @@ We put the visual aspects of our app into ``. In this case, it’s ```html - - Tab 2 - + Tab 2 @@ -201,7 +198,6 @@ Next, open `src/app/tabs/tabs.page.html`. Change the label to “Photos” and t ```html - @@ -220,7 +216,6 @@ Next, open `src/app/tabs/tabs.page.html`. Change the label to “Photos” and t Tab 3 - ``` diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 2cb41f30321..3389362c2f5 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -53,18 +53,17 @@ import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class PhotoService { - - constructor() { } + constructor() {} public async addNewToGallery() { // Take a photo const capturedPhoto = await Camera.getPhoto({ resultType: CameraResultType.Uri, source: CameraSource.Camera, - quality: 100 + quality: 100, }); } } @@ -83,9 +82,8 @@ import { PhotoService } from '../services/photo.service'; standalone: false, }) export class Tab2Page { - // update constructor to include photoService - constructor(public photoService: PhotoService) { } + constructor(public photoService: PhotoService) {} // add addNewToGallery method addPhotoToGallery() { @@ -99,9 +97,7 @@ Then, open `tab2.page.html` and call the `addPhotoToGallery()` function when the ```html - - Tab 2 - + Tab 2 @@ -118,7 +114,6 @@ Then, open `tab2.page.html` and call the `addPhotoToGallery()` function when the - ``` @@ -147,7 +142,7 @@ Back at the top of the `PhotoService` class definition, define an array of Photo export class PhotoService { public photos: UserPhoto[] = []; - constructor() { } + constructor() {} // other code } @@ -170,6 +165,7 @@ public async addNewToGallery() { }); } ``` + `photo.service.ts` should now look like this: ```tsx @@ -179,23 +175,23 @@ import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class PhotoService { public photos: UserPhoto[] = []; - constructor() { } + constructor() {} public async addNewToGallery() { const capturedPhoto = await Camera.getPhoto({ resultType: CameraResultType.Uri, source: CameraSource.Camera, - quality: 100 + quality: 100, }); // add new photo to photos array this.photos.unshift({ - filepath: "soon...", - webviewPath: capturedPhoto.webPath! + filepath: 'soon...', + webviewPath: capturedPhoto.webPath!, }); } } @@ -205,14 +201,13 @@ export interface UserPhoto { webviewPath?: string; } ``` + Next, move over to `tab2.page.html` so we can display the image on the screen. Add a [Grid component](https://ionicframework.com/docs/api/grid) so that each photo will display nicely as photos are added to the gallery, and loop through each photo in the `PhotoServices`'s Photos array, adding an Image component (``) for each. Point the `src` (source) at the photo’s path: ```html - - Tab 2 - + Tab 2 @@ -238,9 +233,9 @@ Next, move over to `tab2.page.html` so we can display the image on the screen. A - ``` + :::note Learn more about the [ngFor core directive](https://blog.angular-university.io/angular-2-ngfor/). ::: diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index a162a608a55..4eef54f42d4 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -86,17 +86,17 @@ import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class PhotoService { public photos: UserPhoto[] = []; - constructor() { } + constructor() {} public async addNewToGallery() { const capturedPhoto = await Camera.getPhoto({ resultType: CameraResultType.Uri, source: CameraSource.Camera, - quality: 100 + quality: 100, }); // Save the picture and add it to photo collection @@ -114,14 +114,14 @@ export class PhotoService { const savedFile = await Filesystem.writeFile({ path: fileName, data: base64Data, - directory: Directory.Data + 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 + webviewPath: photo.webPath, }; } @@ -130,17 +130,18 @@ export class PhotoService { const response = await fetch(photo.webPath!); const blob = await response.blob(); - return await this.convertBlobToBase64(blob) as string; + return (await this.convertBlobToBase64(blob)) as string; } - private convertBlobToBase64 = (blob: Blob) => new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onerror = reject; - reader.onload = () => { + private convertBlobToBase64 = (blob: Blob) => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onerror = reject; + reader.onload = () => { resolve(reader.result); - }; - reader.readAsDataURL(blob); - }); + }; + reader.readAsDataURL(blob); + }); } export interface UserPhoto { @@ -148,7 +149,8 @@ export interface UserPhoto { 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()](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) as a neat way to read the file into blob format, then FileReader’s [readAsDataURL()](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/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. \ No newline at end of file +when the user navigates to the Photos tab. diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index e87abec44cb..15ba3338fcd 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -74,6 +74,7 @@ for (let photo of this.photos) { photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; } ``` + After these updates to the `PhotoService` class, your `photos.service.ts` file should look like this: ```tsx @@ -83,9 +84,8 @@ import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) - export class PhotoService { public photos: UserPhoto[] = []; private PHOTO_STORAGE = 'photos'; @@ -139,14 +139,14 @@ export class PhotoService { const savedFile = await Filesystem.writeFile({ path: fileName, data: base64Data, - directory: Directory.Data + 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 + webviewPath: photo.webPath, }; } @@ -155,17 +155,18 @@ export class PhotoService { const response = await fetch(photo.webPath!); const blob = await response.blob(); - return await this.convertBlobToBase64(blob) as string; + return (await this.convertBlobToBase64(blob)) as string; } - private convertBlobToBase64 = (blob: Blob) => new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onerror = reject; - reader.onload = () => { + private convertBlobToBase64 = (blob: Blob) => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onerror = reject; + reader.onload = () => { resolve(reader.result); - }; - reader.readAsDataURL(blob); - }); + }; + reader.readAsDataURL(blob); + }); } export interface UserPhoto { @@ -187,7 +188,6 @@ import { PhotoService } from '../services/photo.service'; standalone: false, }) export class Tab2Page { - constructor(public photoService: PhotoService) {} // add call to loadSaved on navigation to Photos tab @@ -200,6 +200,7 @@ export class Tab2Page { } } ``` + :::note If you're seeing broken image links or missing photos after following these steps, you may need to open your browser's dev tools and clear both [localStorage](https://developer.chrome.com/docs/devtools/storage/localstorage) and [IndexedDB](https://developer.chrome.com/docs/devtools/storage/indexeddb). diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 4de0c0d0eb4..e1d4b508b62 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -70,7 +70,9 @@ top of `photo.service.ts`. ```tsx import { Capacitor } from '@capacitor/core'; ``` + Then update `savePicture()` to look like the following: + ```tsx // Save picture to file on device private async savePicture(photo: Photo) { From 19727c598832b90f506955d78c5c3b2dfd1fa458 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:19:22 -0500 Subject: [PATCH 07/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 0f14b8a0e2e..62da76d2458 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -189,7 +189,7 @@ We put the visual aspects of our app into ``. In this case, it’s - + ``` From 925be885b03f5a445d36093eb3f117153ac651f8 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:19:46 -0500 Subject: [PATCH 08/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 62da76d2458..981aa51d713 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -112,7 +112,8 @@ Next, import `@ionic/pwa-elements` by editing `src/main.ts`. ```tsx import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; -import { defineCustomElements } from '@ionic/pwa-elements/loader'; // Added import +// CHANGE: Add the following import. +import { defineCustomElements } from '@ionic/pwa-elements/loader'; // Call the element loader before the bootstrapModule/bootstrapApplication call defineCustomElements(window); From 8311b56d2b406de60b696dabe48851101e1502c0 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:20:06 -0500 Subject: [PATCH 09/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 981aa51d713..bb3ab0d4927 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -183,7 +183,7 @@ We put the visual aspects of our app into ``. In this case, it’s - + From dbba4e23b4f7ef93065dcd72c63e5a730227c10a Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:20:20 -0500 Subject: [PATCH 10/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index bb3ab0d4927..30cc1f64dc8 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -206,7 +206,7 @@ Next, open `src/app/tabs/tabs.page.html`. Change the label to “Photos” and t - + Photos From 6a9370940c4b33c59b4191bff87b11bb98a1a0dc Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:20:31 -0500 Subject: [PATCH 11/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 30cc1f64dc8..92c4960c59f 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -208,7 +208,7 @@ Next, open `src/app/tabs/tabs.page.html`. Change the label to “Photos” and t - + Photos From 6fb2f87a4fa2fb85aae8cb84abb70a5eaab043e7 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:20:52 -0500 Subject: [PATCH 12/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/5-adding-mobile.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index e1d4b508b62..2138880602e 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -30,7 +30,7 @@ export class PhotoService { // add property platform to store which platform app is running on private platform: Platform; - // update constructor to set platform property + // CHANGE: Update constructor to set `platform`. constructor(platform: Platform) { this.platform = platform; } From 0bf89778f60b20e22ccbef160e87c91d3eb65973 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:21:08 -0500 Subject: [PATCH 13/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/5-adding-mobile.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 2138880602e..28c55ed299a 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -35,7 +35,7 @@ export class PhotoService { this.platform = platform; } - // other code... + // other code } ``` From 954c32b37b29dfe865f7320f2e8e5ddb502ef6e5 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:30:12 -0500 Subject: [PATCH 14/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/2-taking-photos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 3389362c2f5..1d64398afa3 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -82,7 +82,7 @@ import { PhotoService } from '../services/photo.service'; standalone: false, }) export class Tab2Page { - // update constructor to include photoService + // CHANGE: Update constructor to include `photoService`. constructor(public photoService: PhotoService) {} // add addNewToGallery method From 318444e39208c80cc0ff138e1a899a1d9940d094 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:30:44 -0500 Subject: [PATCH 15/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/2-taking-photos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 1d64398afa3..263fa2f2612 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -85,7 +85,7 @@ export class Tab2Page { // CHANGE: Update constructor to include `photoService`. constructor(public photoService: PhotoService) {} - // add addNewToGallery method + // CHANGE: Add `addNewToGallery` method. addPhotoToGallery() { this.photoService.addNewToGallery(); } From 2b7b18881d97c6462ad9cd8187380c785d654f47 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:31:37 -0500 Subject: [PATCH 16/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/2-taking-photos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 263fa2f2612..8fb80cf3e33 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -109,7 +109,7 @@ Then, open `tab2.page.html` and call the `addPhotoToGallery()` function when the - + From 3a30866cc5cbfd0e19f7d61834b225dfa683a853 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:32:16 -0500 Subject: [PATCH 17/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/2-taking-photos.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 8fb80cf3e33..609ec6bad31 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -127,7 +127,9 @@ After taking a photo, it disappears right away. We need to display it within our ## Displaying Photos -Return to `photo.service.ts`. Outside of the `PhotoService` class definition (the very bottom of the file), create a new interface, `UserPhoto`, to hold our photo metadata: +Return to `photo.service.ts`. + +Outside of the `PhotoService` class definition (the very bottom of the file), create a new interface, `UserPhoto`, to hold our photo metadata: ```tsx export interface UserPhoto { From 1a8352917956c1666fad61ba02e5a944b425c54f Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:33:33 -0500 Subject: [PATCH 18/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/2-taking-photos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 609ec6bad31..760bb70af53 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -138,7 +138,7 @@ export interface UserPhoto { } ``` -Back at the top of the `PhotoService` class definition, define an array of Photos, which will contain a reference to each photo captured with the Camera. +Above the constructor, define an array of `UserPhoto`, which will contain a reference to each photo captured with the Camera. ```tsx export class PhotoService { From 9c926b4e671f88258edf5e55a1ffbb06bd6d78db Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:34:11 -0500 Subject: [PATCH 19/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/2-taking-photos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 760bb70af53..379dcb7628e 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -160,7 +160,7 @@ public async addNewToGallery() { quality: 100 }); - // add new photo to photos array + // CHANGE: Add the new photo to the photos array. this.photos.unshift({ filepath: "soon...", webviewPath: capturedPhoto.webPath! From 082bdef6375a5a3755332c2169d919284a2958e0 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:36:22 -0500 Subject: [PATCH 20/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/2-taking-photos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 379dcb7628e..7901e64bbde 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -220,7 +220,7 @@ Next, move over to `tab2.page.html` so we can display the image on the screen. A - + From ae805bb309e6a047462de16dd9b8f7e27d69ab0c Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:37:04 -0500 Subject: [PATCH 21/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/2-taking-photos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 7901e64bbde..8ebbf9ed6f6 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -223,7 +223,7 @@ Next, move over to `tab2.page.html` so we can display the image on the screen. A - + From 3e774091e0269deb09113d42ca602fbf954f0ce1 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:38:27 -0500 Subject: [PATCH 22/38] docs(angular): remove link to ngFor docs Co-authored-by: Maria Hutt --- docs/angular/your-first-app/2-taking-photos.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 8ebbf9ed6f6..178fed0a175 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -238,10 +238,6 @@ Next, move over to `tab2.page.html` so we can display the image on the screen. A ``` -:::note -Learn more about the [ngFor core directive](https://blog.angular-university.io/angular-2-ngfor/). -::: - Save all files. Within the web browser, click the Camera button and take another photo. This time, the photo is displayed in the Photo Gallery! Up next, we’ll add support for saving the photos to the filesystem, so they can be retrieved and displayed in our app at a later time. From 32fc888b9c04e39d126eb0de08ff92f2c1366ec1 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:41:33 -0500 Subject: [PATCH 23/38] docs(angular): update code block Co-authored-by: Maria Hutt --- docs/angular/your-first-app/3-saving-photos.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index 4eef54f42d4..1d365046f30 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -90,6 +90,7 @@ import { Preferences } from '@capacitor/preferences'; }) export class PhotoService { public photos: UserPhoto[] = []; + constructor() {} public async addNewToGallery() { From c3ee323ccf063c0e5afc8ad56bb478b1bf86db8b Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:42:27 -0500 Subject: [PATCH 24/38] docs(angular): add new line Co-authored-by: Maria Hutt --- docs/angular/your-first-app/3-saving-photos.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index 1d365046f30..f1511b8ee81 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -153,5 +153,7 @@ export interface UserPhoto { 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()](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) as a neat way to read the file into blob format, then FileReader’s [readAsDataURL()](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/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 +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. From 22165d1fbe79317d679a203edcf449843ac73967 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:43:08 -0500 Subject: [PATCH 25/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/4-loading-photos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index 15ba3338fcd..b8f0bbe4b51 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -16,7 +16,7 @@ Open `photo.service.ts` and begin by defining a new property in the `PhotoServic export class PhotoService { public photos: UserPhoto[] = []; - // add key for photo store + // CHANGE: Add a key for photo storage. private PHOTO_STORAGE: string = 'photos'; constructor() {} From 9c6729ffb5cef1dc9ac6db96676579bf6e864eec Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:43:58 -0500 Subject: [PATCH 26/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/4-loading-photos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index b8f0bbe4b51..0847f32aa07 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -39,7 +39,7 @@ public async addNewToGallery() { this.photos.unshift(savedImageFile); - // Add call to set() method to cache all photo data for future retrieval + // CHANGE: Add method to cache all photo data for future retrieval. Preferences.set({ key: this.PHOTO_STORAGE, value: JSON.stringify(this.photos), From a5069fdd7d3e8bb34102250b86e1692b0389c301 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 09:57:01 -0500 Subject: [PATCH 27/38] docs(angular): update code comment Co-authored-by: Maria Hutt --- docs/angular/your-first-app/5-adding-mobile.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 28c55ed299a..7b6dcb80127 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -19,8 +19,7 @@ import { Injectable } from '@angular/core'; import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; - -// add Platform import +// CHANGE: Add import. import { Platform } from '@ionic/angular'; export class PhotoService { From 35db01be3a155fec067d3197aeaf131475e34444 Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 10:09:08 -0500 Subject: [PATCH 28/38] docs(angular): update language Co-authored-by: Maria Hutt --- docs/angular/your-first-app/4-loading-photos.md | 6 ++++-- docs/angular/your-first-app/5-adding-mobile.md | 9 +++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index 0847f32aa07..4ff00ed4c8b 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -175,7 +175,9 @@ export interface UserPhoto { } ``` -Our `PhotoService` can now load the saved images, but we'll need to update `tab2.page.ts` to put that new code to work. We'll call `loadSaved` within the [ngOnInit](https://angular.dev/guide/components/lifecycle#ngoninit) lifecycle method so that when the user first navigates to Tab 2 (the Photo Gallery), all photos are loaded and displayed on the screen. Update `tab2.page.ts` to look like the following: +Our `PhotoService` can now load the saved images, but we'll need to update `tab2.page.ts` to put that new code to work. We'll call `loadSaved` within the [ngOnInit](https://angular.dev/guide/components/lifecycle#ngoninit) lifecycle method so that when the user first navigates to Tab 2 (the Photo Gallery), all photos are loaded and displayed on the screen. + +Update `tab2.page.ts` to look like the following: ```tsx import { Component } from '@angular/core'; @@ -190,7 +192,7 @@ import { PhotoService } from '../services/photo.service'; export class Tab2Page { constructor(public photoService: PhotoService) {} - // add call to loadSaved on navigation to Photos tab + // CHANGE: Add call to `loadSaved` when navigating to the Photos tab. async ngOnInit() { await this.photoService.loadSaved(); } diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 7b6dcb80127..9ab6b0a3c3b 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -26,7 +26,7 @@ export class PhotoService { public photos: UserPhoto[] = []; private PHOTO_STORAGE: string = 'photos'; - // add property platform to store which platform app is running on + // CHANGE: Add a property to track the app's running platform. private platform: Platform; // CHANGE: Update constructor to set `platform`. @@ -40,7 +40,9 @@ export class PhotoService { ## Platform-specific Logic -First, we’ll update the photo saving functionality to support mobile. In the `readAsBase64()` function, check which platform the app is running on. If it’s “hybrid” (Capacitor or Cordova, two native runtimes), then read the photo file into base64 format using the Filesystem `readFile()` method. Otherwise, use the same logic as before when running the app on the web. Update `readAsBase64()` to look like the following: +First, we’ll update the photo saving functionality to support mobile. In the `readAsBase64()` function, check which platform the app is running on. If it’s “hybrid” (Capacitor or Cordova, two native runtimes), then read the photo file into base64 format using the Filesystem `readFile()` method. Otherwise, use the same logic as before when running the app on the web. + +Update `readAsBase64()` to look like the following: ```tsx private async readAsBase64(photo: Photo) { @@ -63,8 +65,7 @@ private async readAsBase64(photo: Photo) { } ``` -Next, update the `savePicture()` method. When running on mobile, set `filepath` to the result of the `writeFile()` operation - `savedFile.uri`. When setting the `webviewPath`, use the special `Capacitor.convertFileSrc()` method ([details on the File Protocol](https://ionicframework.com/docs/core-concepts/webview#file-protocol)). To use this method, we'll need to import Capacitor at the -top of `photo.service.ts`. +Next, update the `savePicture()` method. When running on mobile, set `filepath` to the result of the `writeFile()` operation - `savedFile.uri`. When setting the `webviewPath`, use the special `Capacitor.convertFileSrc()` method ([details on the File Protocol](https://ionicframework.com/docs/core-concepts/webview#file-protocol)). To use this method, we'll need to import Capacitor into `photo.service.ts`. ```tsx import { Capacitor } from '@capacitor/core'; From 16ade0cf25f3ec1dacbaf6344bd0d9f4ad446eaa Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Thu, 26 Jun 2025 10:28:06 -0500 Subject: [PATCH 29/38] docs(angular): remove code block Add context in two previous two code blocks in separate commit. Co-authored-by: Maria Hutt --- .../angular/your-first-app/2-taking-photos.md | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 178fed0a175..83b9a45c114 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -44,31 +44,6 @@ public async addNewToGallery() { Notice the magic here: there's no platform-specific code (web, iOS, or Android)! The Capacitor Camera plugin abstracts that away for us, leaving just one method call - `Camera.getPhoto()` - that will open up the device's camera and allow us to take photos. -Your updated `photo.service.ts` should now look like this: - -```tsx -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 { - constructor() {} - - public async addNewToGallery() { - // Take a photo - const capturedPhoto = await Camera.getPhoto({ - resultType: CameraResultType.Uri, - source: CameraSource.Camera, - quality: 100, - }); - } -} -``` - Next, open up `tab2.page.ts` and import the PhotoService class and add a method that calls the `addNewToGallery` method on the imported service: ```tsx From a21d5e8e92f107fc096334a3440f4da0f511e436 Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Thu, 26 Jun 2025 10:51:22 -0500 Subject: [PATCH 30/38] docs(angular): update code blocks --- .../angular/your-first-app/2-taking-photos.md | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 83b9a45c114..28a72529730 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -24,21 +24,42 @@ ionic g service services/photo Open the new `services/photo.service.ts` file, and let’s add the logic that will power the camera functionality. First, import Capacitor dependencies and get references to the Camera, Filesystem, and Storage plugins: ```tsx +import { Injectable } from '@angular/core'; +// CHANGE: Add the following imports. 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 { + + constructor() { } +} ``` Next, define a new class method, `addNewToGallery`, that will contain the core logic to take a device photo and save it to the filesystem. Let’s start by opening the device camera: ```tsx -public async addNewToGallery() { - // Take a photo - const capturedPhoto = await Camera.getPhoto({ - resultType: CameraResultType.Uri, - source: CameraSource.Camera, - quality: 100 - }); +import { Injectable } from '@angular/core'; +import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Preferences } from '@capacitor/preferences'; + +export class PhotoService { + + constructor() { } + + // CHANGE: Add the gallery function. + public async addNewToGallery() { + // Take a photo + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + quality: 100, + }); + } } ``` From effdaaae24ae36e0ffa258267af7aed4edb16180 Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Thu, 26 Jun 2025 11:15:09 -0500 Subject: [PATCH 31/38] docs(angular): update code blocks --- docs/angular/your-first-app/2-taking-photos.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 28a72529730..7eadf2b55cc 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -128,6 +128,11 @@ Return to `photo.service.ts`. Outside of the `PhotoService` class definition (the very bottom of the file), create a new interface, `UserPhoto`, to hold our photo metadata: ```tsx +export class PhotoService { + // Same old code from before. +} + +// CHANGE: Add the interface. export interface UserPhoto { filepath: string; webviewPath?: string; @@ -138,6 +143,7 @@ Above the constructor, define an array of `UserPhoto`, which will contain a refe ```tsx export class PhotoService { + // CHANGE: Add the photos array. public photos: UserPhoto[] = []; constructor() {} From 2aeb4fb33c1a898f14b80e1a670cc4a5ee811ef5 Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Thu, 26 Jun 2025 11:54:14 -0500 Subject: [PATCH 32/38] docs(angular): update code blocks --- .../angular/your-first-app/3-saving-photos.md | 231 ++++++++++++++---- 1 file changed, 189 insertions(+), 42 deletions(-) diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index f1511b8ee81..d844316eacd 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -11,70 +11,217 @@ We’re now able to take multiple photos and display them in a photo gallery on 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: ```tsx -private async savePicture(photo: 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()`: ```tsx -public async addNewToGallery() { - // Take a photo - const capturedPhoto = await Camera.getPhoto({ - resultType: CameraResultType.Uri, // file-based data; provides best performance - source: CameraSource.Camera, // automatically take a new photo with the camera - quality: 100 // highest quality (0 to 100) - }); +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() { } - // 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); + 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](https://capacitorjs.com/docs/apis/filesystem) 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. ```tsx -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 - }); +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); + } - // Use webPath to display the new image instead of base64 since it's - // already loaded into memory - return { - filepath: fileName, - webviewPath: photo.webPath - }; + // 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: ```tsx -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(); +import { Injectable } from '@angular/core'; +import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Preferences } from '@capacitor/preferences'; - return await this.convertBlobToBase64(blob) as string; -} +@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, + }); -private convertBlobToBase64 = (blob: Blob) => new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onerror = reject; - reader.onload = () => { + // 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); -}); + }; + reader.readAsDataURL(blob); + }); +} + +export interface UserPhoto { + filepath: string; + webviewPath?: string; +} ``` `photo.service.ts` should now look like this: From 20aa15b7f63421657044522185fb10453850a99b Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Thu, 26 Jun 2025 13:30:00 -0500 Subject: [PATCH 33/38] docs(angular): update code blocks --- .../your-first-app/4-loading-photos.md | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index 4ff00ed4c8b..b9f78a12572 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -50,6 +50,11 @@ public async addNewToGallery() { With the photo array data saved, create a new public method in the `PhotoService` class called `loadSaved()` that can retrieve the photo data. We use the same key to retrieve the photos array in JSON format, then parse it into an array: ```tsx +public async addNewToGallery() { + // Same old code from before. +} + +// CHANGE: Add the method to load the photo data. public async loadSaved() { // Retrieve cached photo array data const { value } = await Preferences.get({ key: this.PHOTO_STORAGE }); @@ -57,21 +62,31 @@ public async loadSaved() { // more to come... } + +private async savePicture(photo: Photo) { + // Same old code from before. +} ``` On mobile (coming up next!), we can directly set the source of an image tag - `` - to each photo file on the Filesystem, displaying them automatically. On the web, however, we must read each image from the Filesystem into base64 format, using a new `base64` property on the `Photo` object. This is because the Filesystem API uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood. Add the following code to complete the `loadSaved()` function: ```tsx -// Display the photo by reading into base64 format -for (let photo of this.photos) { - // Read each saved photo's data from the Filesystem - const readFile = await Filesystem.readFile({ - path: photo.filepath, - directory: Directory.Data, - }); +public async loadSaved() { + // Retrieve cached photo array data + const { value } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (value ? JSON.parse(value) : []) as UserPhoto[]; - // Web platform only: Load the photo as base64 data - photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; + // CHANGE: Display the photo by reading into base64 format. + for (let photo of this.photos) { + // Read each saved photo's data from the Filesystem + const readFile = await Filesystem.readFile({ + path: photo.filepath, + directory: Directory.Data, + }); + + // Web platform only: Load the photo as base64 data + photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; + } } ``` From 872a89f04fcd86e21e9b66073a58db3daac47c81 Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Thu, 26 Jun 2025 13:50:45 -0500 Subject: [PATCH 34/38] docs(angular): add context in localStorage and IndexedDB note --- docs/angular/your-first-app/4-loading-photos.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index b9f78a12572..97330176a5d 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -221,5 +221,8 @@ export class Tab2Page { :::note If you're seeing broken image links or missing photos after following these steps, you may need to open your browser's dev tools and clear both [localStorage](https://developer.chrome.com/docs/devtools/storage/localstorage) and [IndexedDB](https://developer.chrome.com/docs/devtools/storage/indexeddb). + +In localStorage, look for domain `http://localhost:8100` and key `CapacitorStorage.photos`. +In IndexedDB, find a store called "FileStorage". Your photos will have a key like `/DATA/123456789012.jpeg`. ::: That’s it! We’ve built a complete Photo Gallery feature in our Ionic app that works on the web. Next up, we’ll transform it into a mobile app for iOS and Android! From a2d98dac0d3c867019c078a8932584c5671b6512 Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Thu, 26 Jun 2025 14:13:35 -0500 Subject: [PATCH 35/38] docs(angular): added code block --- .../angular/your-first-app/5-adding-mobile.md | 132 +++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 9ab6b0a3c3b..4be207cece9 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -132,4 +132,134 @@ public async loadSaved() { } ``` -Our Photo Gallery now consists of one codebase that runs on the web, Android, and iOS. Next up, the part you’ve been waiting for - deploying the app to a device. +Our Photo Gallery now consists of one codebase that runs on the web, Android, and iOS. `photos.service.ts` should now look like this: + +```tsx +import { Injectable } from '@angular/core'; +import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Preferences } from '@capacitor/preferences'; +import { Platform } from '@ionic/angular'; +import { Capacitor } from '@capacitor/core'; + +@Injectable({ + providedIn: 'root' +}) +export class PhotoService { + public photos: UserPhoto[] = []; + private PHOTO_STORAGE: string = 'photos'; + private platform: Platform; + + constructor(platform: Platform) { + this.platform = platform; + } + + 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); + + // Method to cache all photo data for future retrieval. + Preferences.set({ + key: this.PHOTO_STORAGE, + value: JSON.stringify(this.photos), + }) + } + + public async loadSaved() { + // Retrieve cached photo array data + const { value } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (value ? JSON.parse(value) : []) as UserPhoto[]; + + // Easiest way to detect when running on the web: + // “when the platform is NOT hybrid, do this” + if (!this.platform.is('hybrid')) { + // Display the photo by reading into base64 format + for (let photo of this.photos) { + // Read each saved photo's data from the Filesystem + const readFile = await Filesystem.readFile({ + path: photo.filepath, + directory: Directory.Data + }); + + // Web platform only: Load the photo as base64 data + photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; + } + } + } + + // Save picture to file on device + 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 + }); + + if (this.platform.is('hybrid')) { + // Display the new image by rewriting the 'file://' path to HTTP + // Details: https://ionicframework.com/docs/building/webview#file-protocol + return { + filepath: savedFile.uri, + webviewPath: Capacitor.convertFileSrc(savedFile.uri), + }; + } + else { + // 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) { + // "hybrid" will detect Cordova or Capacitor + if (this.platform.is('hybrid')) { + // Read the file into base64 format + const file = await Filesystem.readFile({ + path: photo.path! + }); + + return file.data; + } + else { + // 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; +} +``` + +Next up, the part you’ve been waiting for - deploying the app to a device. From a180caddc545ad13d71bb739a7c8ad4b3e0a7178 Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Thu, 26 Jun 2025 17:00:32 -0500 Subject: [PATCH 36/38] docs(angular): add context to code blocks --- docs/angular/your-first-app/7-live-reload.md | 213 +++++++++++++++---- 1 file changed, 172 insertions(+), 41 deletions(-) diff --git a/docs/angular/your-first-app/7-live-reload.md b/docs/angular/your-first-app/7-live-reload.md index 26fe3b4fbf0..a4e2b4daa43 100644 --- a/docs/angular/your-first-app/7-live-reload.md +++ b/docs/angular/your-first-app/7-live-reload.md @@ -35,49 +35,133 @@ The Live Reload server will start up, and the native IDE of choice will open if With Live Reload running and the app open on your device, let’s implement photo deletion functionality. Open `tab2.page.html` and add a new click handler to each `` element. When the app user taps on a photo in our gallery, we’ll display an [Action Sheet](https://ionicframework.com/docs/api/action-sheet) dialog with the option to either delete the selected photo or cancel (close) the dialog. ```html - - - + + + + Photo Gallery + + + + + + + + Photo Gallery + + + + + + + + + + + + + + + + + + ``` -Over in `tab2.page.ts`, import Action Sheet and add it to the constructor: +Over in `tab2.page.ts`, import `ActionSheetController` and add it to the constructor: ```tsx +import { Component } from '@angular/core'; +import { PhotoService } from '../services/photo.service'; +// CHANGE: Add import import { ActionSheetController } from '@ionic/angular'; -constructor(public photoService: PhotoService, - public actionSheetController: ActionSheetController) {} +@Component({ + selector: 'app-tab2', + templateUrl: 'tab2.page.html', + styleUrls: ['tab2.page.scss'], + standalone: false, +}) +export class Tab2Page { + + // CHANGE: Update constructor to include `actionSheetController`. + constructor(public photoService: PhotoService, + public actionSheetController: ActionSheetController) {} + + // other code +} ``` Add `UserPhoto` to the import statement. ```tsx +import { Component } from '@angular/core'; +// CHANGE: Update import import { PhotoService, UserPhoto } from '../services/photo.service'; +import { ActionSheetController } from '@ionic/angular'; + +@Component({ + selector: 'app-tab2', + templateUrl: 'tab2.page.html', + styleUrls: ['tab2.page.scss'], + standalone: false, +}) +export class Tab2Page { + + constructor(public photoService: PhotoService, + public actionSheetController: ActionSheetController) {} + + // other code +} ``` Next, implement the `showActionSheet()` function. We add two options: `Delete` that calls PhotoService’s `deletePicture()` function (to be added next) and `Cancel`, which when given the role of “cancel” will automatically close the action sheet: ```tsx -public async showActionSheet(photo: UserPhoto, position: number) { - const actionSheet = await this.actionSheetController.create({ - header: 'Photos', - buttons: [{ - text: 'Delete', - role: 'destructive', - icon: 'trash', - handler: () => { - this.photoService.deletePicture(photo, position); - } - }, { - text: 'Cancel', - icon: 'close', - role: 'cancel', - handler: () => { - // Nothing to do, action sheet is automatically closed +import { Component } from '@angular/core'; +import { PhotoService, UserPhoto } from '../services/photo.service'; +import { ActionSheetController } from '@ionic/angular'; + +@Component({ + selector: 'app-tab2', + templateUrl: 'tab2.page.html', + styleUrls: ['tab2.page.scss'], + standalone: false, +}) +export class Tab2Page { + + constructor(public photoService: PhotoService, + public actionSheetController: ActionSheetController) {} + + addPhotoToGallery() { + this.photoService.addNewToGallery(); + } + + async ngOnInit() { + await this.photoService.loadSaved(); + } + + // CHANGE: Add `showActionSheet` function. + public async showActionSheet(photo: UserPhoto, position: number) { + const actionSheet = await this.actionSheetController.create({ + header: 'Photos', + buttons: [{ + text: 'Delete', + role: 'destructive', + icon: 'trash', + handler: () => { + this.photoService.deletePicture(photo, position); } - }] - }); - await actionSheet.present(); + }, { + text: 'Cancel', + icon: 'close', + role: 'cancel', + handler: () => { + // Nothing to do, action sheet is automatically closed + } + }] + }); + await actionSheet.present(); + } } ``` @@ -86,24 +170,71 @@ Save both of the files we just edited. The Photo Gallery app will reload automat In `src/app/services/photo.service.ts`, add the `deletePicture()` function: ```tsx -public async deletePicture(photo: UserPhoto, position: number) { - // Remove this photo from the Photos reference data array - this.photos.splice(position, 1); - - // Update photos array cache by overwriting the existing photo array - Preferences.set({ - key: this.PHOTO_STORAGE, - value: JSON.stringify(this.photos) +import { Injectable } from '@angular/core'; +import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Preferences } from '@capacitor/preferences'; +import { Platform } from '@ionic/angular'; +import { Capacitor } from '@capacitor/core'; + +@Injectable({ + providedIn: 'root' +}) +export class PhotoService { + public photos: UserPhoto[] = []; + private PHOTO_STORAGE: string = 'photos'; + private platform: Platform; + + constructor(platform: Platform) { + this.platform = platform; + } + + public async addNewToGallery() { + // Same old code from before. + } + + public async loadSaved() { + // Same old code from before. + } + + // Save picture to file on device + private async savePicture(photo: Photo) { + // Same old code from before. + } + + // CHANGE: Add the `deletePicture` function. + public async deletePicture(photo: UserPhoto, position: number) { + // Remove this photo from the Photos reference data array + this.photos.splice(position, 1); + + // Update photos array cache by overwriting the existing photo array + Preferences.set({ + key: this.PHOTO_STORAGE, + value: JSON.stringify(this.photos) + }); + + // Delete photo file from filesystem + const filename = photo.filepath + .substr(photo.filepath.lastIndexOf('/') + 1); + + await Filesystem.deleteFile({ + path: filename, + directory: Directory.Data + }); + } + + private async readAsBase64(photo: Photo) { + // Same old code from before. + } + + private convertBlobToBase64 = (blob: Blob) => new Promise((resolve, reject) => { + // Same old code from before. }); +} - // delete photo file from filesystem - const filename = photo.filepath - .substr(photo.filepath.lastIndexOf('/') + 1); - - await Filesystem.deleteFile({ - path: filename, - directory: Directory.Data - }); +export interface UserPhoto { + filepath: string; + webviewPath?: string; } ``` From 977db9171ad9415e34e917e19c051fc11003332f Mon Sep 17 00:00:00 2001 From: Colin Bares Date: Mon, 30 Jun 2025 09:21:47 -0500 Subject: [PATCH 37/38] docs(angular): truncate longer code blocks, fix typos Co-authored-by: Maria Hutt --- .../angular/your-first-app/3-saving-photos.md | 75 +------------------ .../your-first-app/4-loading-photos.md | 6 +- .../angular/your-first-app/5-adding-mobile.md | 4 +- docs/angular/your-first-app/7-live-reload.md | 38 ++-------- 4 files changed, 14 insertions(+), 109 deletions(-) diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index d844316eacd..79e9c1278d1 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -21,23 +21,7 @@ import { Preferences } from '@capacitor/preferences'; 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! - }); - } + // Same old code from before. // CHANGE: Add the `savePicture` method. private async savePicture(photo: Photo) { } @@ -102,23 +86,7 @@ import { Preferences } from '@capacitor/preferences'; 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); - } + // Same old code from before. // CHANGE: Update the `savePicture` method. private async savePicture(photo: Photo) { @@ -160,43 +128,7 @@ import { Preferences } from '@capacitor/preferences'; 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 - }; - } + // Same old code from before. // CHANGE: Add the `readAsBase64` method. private async readAsBase64(photo: Photo) { @@ -303,4 +235,3 @@ Obtaining the camera photo as base64 format on the web appears to be a bit trick 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. diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index 97330176a5d..3c37ab06210 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -219,10 +219,8 @@ export class Tab2Page { ``` :::note -If you're seeing broken image links or missing photos after following these steps, you may need to open your browser's -dev tools and clear both [localStorage](https://developer.chrome.com/docs/devtools/storage/localstorage) and [IndexedDB](https://developer.chrome.com/docs/devtools/storage/indexeddb). +If you're seeing broken image links or missing photos after following these steps, you may need to open your browser's dev tools and clear both [localStorage](https://developer.chrome.com/docs/devtools/storage/localstorage) and [IndexedDB](https://developer.chrome.com/docs/devtools/storage/indexeddb). -In localStorage, look for domain `http://localhost:8100` and key `CapacitorStorage.photos`. -In IndexedDB, find a store called "FileStorage". Your photos will have a key like `/DATA/123456789012.jpeg`. +In localStorage, look for domain `http://localhost:8100` and key `CapacitorStorage.photos`. In IndexedDB, find a store called "FileStorage". Your photos will have a key like `/DATA/123456789012.jpeg`. ::: That’s it! We’ve built a complete Photo Gallery feature in our Ionic app that works on the web. Next up, we’ll transform it into a mobile app for iOS and Android! diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 4be207cece9..123e8a5b549 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -132,7 +132,9 @@ public async loadSaved() { } ``` -Our Photo Gallery now consists of one codebase that runs on the web, Android, and iOS. `photos.service.ts` should now look like this: +Our Photo Gallery now consists of one codebase that runs on the web, Android, and iOS. + +`photos.service.ts` should now look like this: ```tsx import { Injectable } from '@angular/core'; diff --git a/docs/angular/your-first-app/7-live-reload.md b/docs/angular/your-first-app/7-live-reload.md index a4e2b4daa43..1673979fdb4 100644 --- a/docs/angular/your-first-app/7-live-reload.md +++ b/docs/angular/your-first-app/7-live-reload.md @@ -53,7 +53,7 @@ With Live Reload running and the app open on your device, let’s implement phot - + @@ -72,7 +72,7 @@ Over in `tab2.page.ts`, import `ActionSheetController` and add it to the constru ```tsx import { Component } from '@angular/core'; import { PhotoService } from '../services/photo.service'; -// CHANGE: Add import +// CHANGE: Add import. import { ActionSheetController } from '@ionic/angular'; @Component({ @@ -95,7 +95,7 @@ Add `UserPhoto` to the import statement. ```tsx import { Component } from '@angular/core'; -// CHANGE: Update import +// CHANGE: Update import. import { PhotoService, UserPhoto } from '../services/photo.service'; import { ActionSheetController } from '@ionic/angular'; @@ -128,17 +128,7 @@ import { ActionSheetController } from '@ionic/angular'; standalone: false, }) export class Tab2Page { - - constructor(public photoService: PhotoService, - public actionSheetController: ActionSheetController) {} - - addPhotoToGallery() { - this.photoService.addNewToGallery(); - } - - async ngOnInit() { - await this.photoService.loadSaved(); - } + // Same old code from before. // CHANGE: Add `showActionSheet` function. public async showActionSheet(photo: UserPhoto, position: number) { @@ -181,21 +171,7 @@ import { Capacitor } from '@capacitor/core'; providedIn: 'root' }) export class PhotoService { - public photos: UserPhoto[] = []; - private PHOTO_STORAGE: string = 'photos'; - private platform: Platform; - - constructor(platform: Platform) { - this.platform = platform; - } - - public async addNewToGallery() { - // Same old code from before. - } - - public async loadSaved() { - // Same old code from before. - } + // Same old code from before. // Save picture to file on device private async savePicture(photo: Photo) { @@ -227,9 +203,7 @@ export class PhotoService { // Same old code from before. } - private convertBlobToBase64 = (blob: Blob) => new Promise((resolve, reject) => { - // Same old code from before. - }); + // Same old code from before. } export interface UserPhoto { From ff70a525f3500836fba61e23fe9bde202142851a Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Mon, 30 Jun 2025 09:28:08 -0500 Subject: [PATCH 38/38] docs(angular): run linter --- .../angular/your-first-app/2-taking-photos.md | 8 +-- .../angular/your-first-app/3-saving-photos.md | 42 ++++++------- .../angular/your-first-app/5-adding-mobile.md | 39 ++++++------ docs/angular/your-first-app/7-live-reload.md | 60 +++++++++---------- 4 files changed, 71 insertions(+), 78 deletions(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 7eadf2b55cc..bd19fec7fce 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -31,11 +31,10 @@ import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class PhotoService { - - constructor() { } + constructor() {} } ``` @@ -48,8 +47,7 @@ import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; export class PhotoService { - - constructor() { } + constructor() {} // CHANGE: Add the gallery function. public async addNewToGallery() { diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index 79e9c1278d1..438377f622c 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -11,20 +11,19 @@ We’re now able to take multiple photos and display them in a photo gallery on 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: ```tsx - 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' + providedIn: 'root', }) export class PhotoService { // Same old code from before. // CHANGE: Add the `savePicture` method. - private async savePicture(photo: Photo) { } + private async savePicture(photo: Photo) {} } export interface UserPhoto { @@ -42,12 +41,12 @@ import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class PhotoService { public photos: UserPhoto[] = []; - - constructor() { } + + constructor() {} public async addNewToGallery() { // Take a photo @@ -57,7 +56,7 @@ export class PhotoService { quality: 100, }); - // CHANGE: Add `savedImageFile` + // CHANGE: Add `savedImageFile` // Save the picture and add it to photo collection const savedImageFile = await this.savePicture(capturedPhoto); @@ -65,7 +64,7 @@ export class PhotoService { this.photos.unshift(savedImageFile); } - private async savePicture(photo: Photo) { } + private async savePicture(photo: Photo) {} } export interface UserPhoto { @@ -83,7 +82,7 @@ import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class PhotoService { // Same old code from before. @@ -98,14 +97,14 @@ export class PhotoService { const savedFile = await Filesystem.writeFile({ path: fileName, data: base64Data, - directory: Directory.Data + 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 + webviewPath: photo.webPath, }; } } @@ -125,7 +124,7 @@ import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class PhotoService { // Same old code from before. @@ -136,18 +135,19 @@ export class PhotoService { const response = await fetch(photo.webPath!); const blob = await response.blob(); - return await this.convertBlobToBase64(blob) as string; + 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); - }); + 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 { diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 123e8a5b549..810095f917a 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -132,7 +132,7 @@ public async loadSaved() { } ``` -Our Photo Gallery now consists of one codebase that runs on the web, Android, and iOS. +Our Photo Gallery now consists of one codebase that runs on the web, Android, and iOS. `photos.service.ts` should now look like this: @@ -145,7 +145,7 @@ import { Platform } from '@ionic/angular'; import { Capacitor } from '@capacitor/core'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class PhotoService { public photos: UserPhoto[] = []; @@ -173,7 +173,7 @@ export class PhotoService { Preferences.set({ key: this.PHOTO_STORAGE, value: JSON.stringify(this.photos), - }) + }); } public async loadSaved() { @@ -189,7 +189,7 @@ export class PhotoService { // Read each saved photo's data from the Filesystem const readFile = await Filesystem.readFile({ path: photo.filepath, - directory: Directory.Data + directory: Directory.Data, }); // Web platform only: Load the photo as base64 data @@ -208,7 +208,7 @@ export class PhotoService { const savedFile = await Filesystem.writeFile({ path: fileName, data: base64Data, - directory: Directory.Data + directory: Directory.Data, }); if (this.platform.is('hybrid')) { @@ -218,13 +218,12 @@ export class PhotoService { filepath: savedFile.uri, webviewPath: Capacitor.convertFileSrc(savedFile.uri), }; - } - else { + } else { // Use webPath to display the new image instead of base64 since it's // already loaded into memory return { filepath: fileName, - webviewPath: photo.webPath + webviewPath: photo.webPath, }; } } @@ -234,28 +233,28 @@ export class PhotoService { if (this.platform.is('hybrid')) { // Read the file into base64 format const file = await Filesystem.readFile({ - path: photo.path! + path: photo.path!, }); return file.data; - } - else { + } else { // 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; + 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); - }); + 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 { diff --git a/docs/angular/your-first-app/7-live-reload.md b/docs/angular/your-first-app/7-live-reload.md index 1673979fdb4..7cdc8dd82de 100644 --- a/docs/angular/your-first-app/7-live-reload.md +++ b/docs/angular/your-first-app/7-live-reload.md @@ -37,9 +37,7 @@ With Live Reload running and the app open on your device, let’s implement phot ```html - - Photo Gallery - + Photo Gallery @@ -58,7 +56,7 @@ With Live Reload running and the app open on your device, let’s implement phot - + @@ -82,10 +80,8 @@ import { ActionSheetController } from '@ionic/angular'; standalone: false, }) export class Tab2Page { - // CHANGE: Update constructor to include `actionSheetController`. - constructor(public photoService: PhotoService, - public actionSheetController: ActionSheetController) {} + constructor(public photoService: PhotoService, public actionSheetController: ActionSheetController) {} // other code } @@ -106,9 +102,7 @@ import { ActionSheetController } from '@ionic/angular'; standalone: false, }) export class Tab2Page { - - constructor(public photoService: PhotoService, - public actionSheetController: ActionSheetController) {} + constructor(public photoService: PhotoService, public actionSheetController: ActionSheetController) {} // other code } @@ -129,26 +123,29 @@ import { ActionSheetController } from '@ionic/angular'; }) export class Tab2Page { // Same old code from before. - + // CHANGE: Add `showActionSheet` function. public async showActionSheet(photo: UserPhoto, position: number) { const actionSheet = await this.actionSheetController.create({ header: 'Photos', - buttons: [{ - text: 'Delete', - role: 'destructive', - icon: 'trash', - handler: () => { - this.photoService.deletePicture(photo, position); - } - }, { - text: 'Cancel', - icon: 'close', - role: 'cancel', - handler: () => { - // Nothing to do, action sheet is automatically closed - } - }] + buttons: [ + { + text: 'Delete', + role: 'destructive', + icon: 'trash', + handler: () => { + this.photoService.deletePicture(photo, position); + }, + }, + { + text: 'Cancel', + icon: 'close', + role: 'cancel', + handler: () => { + // Nothing to do, action sheet is automatically closed + }, + }, + ], }); await actionSheet.present(); } @@ -168,7 +165,7 @@ import { Platform } from '@ionic/angular'; import { Capacitor } from '@capacitor/core'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class PhotoService { // Same old code from before. @@ -186,19 +183,18 @@ export class PhotoService { // Update photos array cache by overwriting the existing photo array Preferences.set({ key: this.PHOTO_STORAGE, - value: JSON.stringify(this.photos) + value: JSON.stringify(this.photos), }); // Delete photo file from filesystem - const filename = photo.filepath - .substr(photo.filepath.lastIndexOf('/') + 1); + const filename = photo.filepath.substr(photo.filepath.lastIndexOf('/') + 1); await Filesystem.deleteFile({ path: filename, - directory: Directory.Data + directory: Directory.Data, }); } - + private async readAsBase64(photo: Photo) { // Same old code from before. }