From 9c0859d030f71a00ec61f885ca61f8276423b78b Mon Sep 17 00:00:00 2001 From: soundproofboot Date: Fri, 13 Jun 2025 13:14:36 -0500 Subject: [PATCH 01/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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/68] 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. } From 7628df1e97787590365663dfecface2cb0f5b987 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 21 Oct 2025 16:19:16 -0700 Subject: [PATCH 39/68] docs(angular): update your first app page --- docs/angular/your-first-app.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 92c4960c59f..4f6e7416f08 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -47,9 +47,8 @@ Download and install these right away to ensure an optimal Ionic development exp - **Node.js** for interacting with the Ionic ecosystem. [Download the LTS version here](https://nodejs.org/en/). - **A code editor** for... writing code! We are fans of [Visual Studio Code](https://code.visualstudio.com/). - **Command-line interface/terminal (CLI)**: - - **Windows** users: for the best Ionic experience, we recommend the built-in command line (cmd) or the Powershell - CLI, running in Administrator mode. - - **Mac/Linux** users, virtually any terminal will work. + - **Windows** users: for the best Ionic experience, we recommend the built-in command line (cmd) or the Powershell CLI, running in Administrator mode. + - **Mac/Linux** users: virtually any terminal will work. ## Install Ionic Tooling @@ -99,7 +98,7 @@ npm install @capacitor/camera @capacitor/preferences @capacitor/filesystem ### PWA Elements -Some Capacitor plugins, including the Camera API, provide the web-based functionality and UI via the Ionic [PWA Elements library](https://github.com/ionic-team/ionic-pwa-elements). +Some Capacitor plugins, including the Camera API, provide the web-based functionality and UI via the Ionic [PWA Elements library](https://github.com/ionic-team/pwa-elements). It's a separate dependency, so install it next: @@ -109,13 +108,13 @@ npm install @ionic/pwa-elements Next, import `@ionic/pwa-elements` by editing `src/main.ts`. -```tsx +```ts import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; // CHANGE: Add the following import. import { defineCustomElements } from '@ionic/pwa-elements/loader'; -// Call the element loader before the bootstrapModule/bootstrapApplication call +// Call the element loader before the `bootstrapModule` call. defineCustomElements(window); platformBrowserDynamic() @@ -141,7 +140,7 @@ There are three tabs. Click on the Tab2 tab. It’s a blank canvas, aka the perf ![Animated GIF showing the live reload feature in an Ionic app, with changes in code immediately updating the app in a web browser.](/img/guides/first-app-cap-ng/email-photogallery.gif 'Live Reload Feature in Ionic App') -Open the photo-gallery app folder in your code editor of choice, then navigate to `/src/app/tab2/tab2.page.html`. We see: +Open `/src/app/tab2/tab2.page.html`. We see: ```html @@ -172,14 +171,14 @@ We put the visual aspects of our app into ``. In this case, it’s ```html - Tab 2 + Photo Gallery - Tab 2 + Photo Gallery @@ -220,4 +219,4 @@ Next, open `src/app/tabs/tabs.page.html`. Change the label to “Photos” and t ``` -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. +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 c62cb45dd2476aac4c3e93a412e72fd065ce8b01 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 22 Oct 2025 15:02:38 -0700 Subject: [PATCH 40/68] docs(angular): update your first app page --- docs/angular/your-first-app.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 4f6e7416f08..a518093bc29 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -98,7 +98,7 @@ npm install @capacitor/camera @capacitor/preferences @capacitor/filesystem ### PWA Elements -Some Capacitor plugins, including the Camera API, provide the web-based functionality and UI via the Ionic [PWA Elements library](https://github.com/ionic-team/pwa-elements). +Some Capacitor plugins, including the [Camera API](../native/camera.md), provide the web-based functionality and UI via the Ionic [PWA Elements library](https://github.com/ionic-team/pwa-elements). It's a separate dependency, so install it next: @@ -136,7 +136,7 @@ And voilà! Your Ionic app is now running in a web browser. Most of your app can ## Photo Gallery!!! -There are three tabs. Click on the Tab2 tab. It’s a blank canvas, aka the perfect spot to transform into a Photo Gallery. The Ionic CLI features Live Reload, so when you make changes and save them, the app is updated immediately! +There are three tabs. Click on the "Tab2" tab. It’s a blank canvas, aka the perfect spot to transform into a Photo Gallery. The Ionic CLI features Live Reload, so when you make changes and save them, the app is updated immediately! ![Animated GIF showing the live reload feature in an Ionic app, with changes in code immediately updating the app in a web browser.](/img/guides/first-app-cap-ng/email-photogallery.gif 'Live Reload Feature in Ionic App') @@ -160,13 +160,13 @@ Open `/src/app/tab2/tab2.page.html`. We see: ``` -`ion-header` represents the top navigation and toolbar, with "Tab 2" as the title (there are two of them due to iOS [Collapsible Large Title](https://ionicframework.com/docs/api/title#collapsible-large-titles) support). Rename both `ion-title` elements to: +`ion-header` represents the top navigation and toolbar, with "Tab 2" as the title (there are two of them due to iOS [Collapsible Large Title](../api/title.md#collapsible-large-titles) support). Rename both `ion-title` elements to: ```html Photo Gallery ``` -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. +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](../api/fab.md) (FAB) to the bottom of the page and set the camera image as the icon. ```html @@ -194,7 +194,7 @@ We put the visual aspects of our app into ``. In this case, it’s ``` -Next, open `src/app/tabs/tabs.page.html`. Change the label to “Photos” and the icon name to “images”: +Next, open `src/app/tabs/tabs.page.html`. Change the label to “Photos” and the `ellipse` icon to `images` for the middle tab button. ```html From 444aeb15a675009e4b0bca4ab5da3deb882d2739 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 22 Oct 2025 16:52:46 -0700 Subject: [PATCH 41/68] docs(angular): update taking photos page --- .../angular/your-first-app/2-taking-photos.md | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index bd19fec7fce..f3c40f8808a 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -18,12 +18,12 @@ Now for the fun part - adding the ability to take photos with the device’s cam All Capacitor logic (Camera usage and other native features) will be encapsulated in a service class. Create `PhotoService` using the `ionic generate` command: ```shell -ionic g service services/photo +ionic g service services/photo.service ``` 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 +```ts import { Injectable } from '@angular/core'; // CHANGE: Add the following imports. import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; @@ -33,22 +33,18 @@ import { Preferences } from '@capacitor/preferences'; @Injectable({ providedIn: 'root', }) -export class PhotoService { - constructor() {} -} +export class PhotoService {} ``` 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 +```ts 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 @@ -63,10 +59,11 @@ export class PhotoService { 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. -Next, open up `tab2.page.ts` and import the PhotoService class and add a method that calls the `addNewToGallery` method on the imported service: +Next, in `tab2.page.ts`, import the `PhotoService` class and add a method to call its `addNewToGallery` method. -```tsx +```ts import { Component } from '@angular/core'; +// CHANGE: Import the PhotoService. import { PhotoService } from '../services/photo.service'; @Component({ @@ -86,19 +83,19 @@ export class Tab2Page { } ``` -Then, open `tab2.page.html` and call the `addPhotoToGallery()` function when the FAB is tapped/clicked: +Then, open `tab2.page.html` and call the `addPhotoToGallery()` method when the FAB is tapped/clicked: ```html - Tab 2 + Photo Gallery - + - Tab 2 + Photo Gallery @@ -111,7 +108,7 @@ Then, open `tab2.page.html` and call the `addPhotoToGallery()` function when the ``` -Save the file, and if it's not running already, restart the development server in your browser by running `ionic serve`. On the Photo Gallery tab, click the Camera button. If your computer has a webcam of any sort, a modal window appears. Take a selfie! +If it's not running already, restart the development server in your browser by running `ionic serve`. On the Photo Gallery tab, click the Camera button. If your computer has a webcam of any sort, a modal window appears. Take a selfie! ![A photo gallery app displaying a webcam selfie.](/img/guides/first-app-cap-ng/camera-web.png 'Webcam Selfie in Photo Gallery') @@ -125,7 +122,7 @@ 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 +```ts export class PhotoService { // Same old code from before. } @@ -137,22 +134,22 @@ export interface UserPhoto { } ``` -Above the constructor, define an array of `UserPhoto`, which will contain a reference to each photo captured with the Camera. +Above the `addNewToGallery()` method, define an array of `UserPhoto`, which will contain a reference to each photo captured with the Camera. -```tsx +```ts export class PhotoService { // CHANGE: Add the photos array. public photos: UserPhoto[] = []; - constructor() {} - - // other code + public async addNewToGallery() { + // Same old code from before. + } } ``` -Over in the `addNewToGallery` function, add the newly captured photo to the beginning of the Photos array. +Over in the `addNewToGallery` method, add the newly captured photo to the beginning of the Photos array. -```tsx +```ts public async addNewToGallery() { const capturedPhoto = await Camera.getPhoto({ resultType: CameraResultType.Uri, @@ -170,8 +167,9 @@ public async addNewToGallery() { `photo.service.ts` should now look like this: -```tsx +```ts 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'; @@ -180,17 +178,19 @@ import { Preferences } from '@capacitor/preferences'; providedIn: 'root', }) export class PhotoService { + // CHANGE: Add the photos array. public photos: UserPhoto[] = []; - 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, }); - // add new photo to photos array + // CHANGE: Add the new photo to the photos array. this.photos.unshift({ filepath: 'soon...', webviewPath: capturedPhoto.webPath!, @@ -198,25 +198,26 @@ export class PhotoService { } } +// CHANGE: Add the interface. 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: +Next, switch to `tab2.page.html` to display the images. We'll add a [Grid component](../../api/grid.md) to ensure the photos display neatly as they're added to the gallery. Inside the grid, loop through each photo in the `PhotoService`'s `photos` array. For each item, add an [Image component](../../api/img.md) and set its `src` property to the photo's path. ```html - Tab 2 + Photo Gallery - + - Tab 2 + Photo Gallery @@ -238,6 +239,6 @@ Next, move over to `tab2.page.html` so we can display the image on the screen. A ``` -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! +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 9a5f1cbfb960df7f1adfccf03e6cb07997b154d8 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 23 Oct 2025 14:07:43 -0700 Subject: [PATCH 42/68] docs(angular): update saving photos page --- .../angular/your-first-app/3-saving-photos.md | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index 438377f622c..c2c2a536c31 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -1,4 +1,5 @@ --- +title: Saving Photos to the Filesystem sidebar_label: Saving Photos --- @@ -8,9 +9,9 @@ We’re now able to take multiple photos and display them in a photo gallery on ## Filesystem API -Fortunately, saving them to the filesystem only takes a few steps. Begin by creating a new class method, `savePicture()`, in the `PhotoService` class (`src/app/services/photo.service.ts`). We pass in the `photo` object, which represents the newly captured device photo: +Fortunately, saving them to the filesystem only takes a few steps. Begin by creating a new class method, `savePicture()`, in the `PhotoService` class. We pass in the `photo` object, which represents the newly captured device photo: -```tsx +```ts import { Injectable } from '@angular/core'; import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; import { Filesystem, Directory } from '@capacitor/filesystem'; @@ -23,7 +24,12 @@ export class PhotoService { // Same old code from before. // CHANGE: Add the `savePicture` method. - private async savePicture(photo: Photo) {} + private async savePicture(photo: Photo) { + return { + filepath: 'soon...', + webviewPath: 'soon...', + }; + } } export interface UserPhoto { @@ -32,9 +38,9 @@ export interface UserPhoto { } ``` -We can use this new method immediately in `addNewToGallery()`: +We can use this new method immediately in `addNewToGallery()`. -```tsx +```ts import { Injectable } from '@angular/core'; import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; import { Filesystem, Directory } from '@capacitor/filesystem'; @@ -56,7 +62,7 @@ export class PhotoService { quality: 100, }); - // CHANGE: Add `savedImageFile` + // CHANGE: Add `savedImageFile` to save the picture and add it to photo collection. // Save the picture and add it to photo collection const savedImageFile = await this.savePicture(capturedPhoto); @@ -64,7 +70,12 @@ export class PhotoService { this.photos.unshift(savedImageFile); } - private async savePicture(photo: Photo) {} + private async savePicture(photo: Photo) { + return { + filepath: 'soon...', + webviewPath: 'soon...', + }; + } } export interface UserPhoto { @@ -73,9 +84,13 @@ export interface UserPhoto { } ``` -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. +We'll use the Capacitor [Filesystem API](../../native/filesystem.md) to save the photo. First, convert the photo to base64 format using a helper method we'll define: `readAsBase64()`. + +Then, pass the data to the Filesystem's `writeFile` method. Recall that we display photos by setting the image's source path (`src`) to the `webviewPath` property. So, set the `webviewPath` and return the new `Photo` object. -```tsx +The `readAsBase64()` method is necessary because it isolates a small amount of platform-specific logic (more on that soon). For now, create two new helper methods, `readAsBase64()` and `convertBlobToBase64()`, to implement the necessary logic for running on the web. + +```ts import { Injectable } from '@angular/core'; import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; import { Filesystem, Directory } from '@capacitor/filesystem'; @@ -107,27 +122,6 @@ export class PhotoService { 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 -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 { - // Same old code from before. // CHANGE: Add the `readAsBase64` method. private async readAsBase64(photo: Photo) { @@ -139,8 +133,8 @@ export class PhotoService { } // CHANGE: Add the `convertBlobToBase64` method. - private convertBlobToBase64 = (blob: Blob) => - new Promise((resolve, reject) => { + private convertBlobToBase64(blob: Blob) { + return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = reject; reader.onload = () => { @@ -148,6 +142,7 @@ export class PhotoService { }; reader.readAsDataURL(blob); }); + } } export interface UserPhoto { @@ -158,7 +153,7 @@ export interface UserPhoto { `photo.service.ts` should now look like this: -```tsx +```ts import { Injectable } from '@angular/core'; import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; import { Filesystem, Directory } from '@capacitor/filesystem'; @@ -170,9 +165,8 @@ import { Preferences } from '@capacitor/preferences'; export class PhotoService { public photos: UserPhoto[] = []; - constructor() {} - public async addNewToGallery() { + // Take a photo const capturedPhoto = await Camera.getPhoto({ resultType: CameraResultType.Uri, source: CameraSource.Camera, @@ -181,7 +175,7 @@ export class PhotoService { // 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); } @@ -213,8 +207,8 @@ export class PhotoService { return (await this.convertBlobToBase64(blob)) as string; } - private convertBlobToBase64 = (blob: Blob) => - new Promise((resolve, reject) => { + private convertBlobToBase64(blob: Blob) { + return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = reject; reader.onload = () => { @@ -222,6 +216,7 @@ export class PhotoService { }; reader.readAsDataURL(blob); }); + } } export interface UserPhoto { From b43acd1434ac77baa313e8950a53cb938100bf45 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 23 Oct 2025 15:17:18 -0700 Subject: [PATCH 43/68] docs(angular): remove constructor() --- docs/angular/your-first-app/3-saving-photos.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index c2c2a536c31..6fb10b55485 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -51,9 +51,7 @@ import { Preferences } from '@capacitor/preferences'; }) export class PhotoService { public photos: UserPhoto[] = []; - - constructor() {} - +dd public async addNewToGallery() { // Take a photo const capturedPhoto = await Camera.getPhoto({ From 45957b496bb1cb0a2ce039c7d0c760523b5ec668 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 23 Oct 2025 15:42:06 -0700 Subject: [PATCH 44/68] docs(angular): update loading photos page --- .../your-first-app/4-loading-photos.md | 124 ++++++++---------- 1 file changed, 55 insertions(+), 69 deletions(-) diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index 3c37ab06210..372bcea4432 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -1,4 +1,5 @@ --- +title: Loading Photos from the Filesystem sidebar_label: Loading Photos --- @@ -6,29 +7,28 @@ sidebar_label: Loading Photos We’ve implemented photo taking and saving to the filesystem. There’s one last piece of functionality missing: the photos are stored in the filesystem, but we need a way to save pointers to each file so that they can be displayed again in the photo gallery. -Fortunately, this is easy: we’ll leverage the Capacitor [Preferences API](https://capacitorjs.com/docs/apis/preferences) to store our array of Photos in a key-value store. +Fortunately, this is easy: we’ll leverage the Capacitor [Preferences API](../../native/preferences.md) to store our array of Photos in a key-value store. ## Preferences API 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 +```ts export class PhotoService { public photos: UserPhoto[] = []; // CHANGE: Add a key for photo storage. private PHOTO_STORAGE: string = 'photos'; - constructor() {} - - // other code... + // Same old code from before. } ``` -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. +Next, at the end of the `addNewToGallery` method, 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 +```ts public async addNewToGallery() { + // Take a photo const capturedPhoto = await Camera.getPhoto({ resultType: CameraResultType.Uri, source: CameraSource.Camera, @@ -47,52 +47,51 @@ 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. -} +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: -// 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 }); - this.photos = (value ? JSON.parse(value) : []) as UserPhoto[]; - - // more to come... -} +```ts +export class PhotoService { + // Same old code from before. -private async savePicture(photo: Photo) { - // 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 }); + this.photos = (value ? JSON.parse(value) : []) as UserPhoto[]; + } } ``` -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: +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()` method: -```tsx -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[]; +```ts +export class PhotoService { + // Same old code from before. - // 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, - }); + // CHANGE: Update the `loadSaved` method. + 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[]; + + // 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}`; + // Web platform only: Load the photo as base64 data + photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; + } } } ``` -After these updates to the `PhotoService` class, your `photos.service.ts` file should look like this: +`photo.service.ts` should now look like this: -```tsx +```ts import { Injectable } from '@angular/core'; import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; import { Filesystem, Directory } from '@capacitor/filesystem'; @@ -103,48 +102,27 @@ import { Preferences } from '@capacitor/preferences'; }) export class PhotoService { public photos: UserPhoto[] = []; - private PHOTO_STORAGE = 'photos'; - constructor() {} + private PHOTO_STORAGE: string = 'photos'; 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) + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + quality: 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); @@ -173,8 +151,8 @@ export class PhotoService { return (await this.convertBlobToBase64(blob)) as string; } - private convertBlobToBase64 = (blob: Blob) => - new Promise((resolve, reject) => { + private convertBlobToBase64(blob: Blob) { + return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = reject; reader.onload = () => { @@ -182,6 +160,13 @@ export class PhotoService { }; reader.readAsDataURL(blob); }); + } + + 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[]; + } } export interface UserPhoto { @@ -190,11 +175,11 @@ 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. +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 the Photo Gallery, all photos are loaded and displayed on the screen. Update `tab2.page.ts` to look like the following: -```tsx +```ts import { Component } from '@angular/core'; import { PhotoService } from '../services/photo.service'; @@ -223,4 +208,5 @@ If you're seeing broken image links or missing photos after following these step 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 d954a3dffecf9c22d1494afd4dd579176bea4190 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 23 Oct 2025 15:42:36 -0700 Subject: [PATCH 45/68] docs(angular): remove extra --- docs/angular/your-first-app/3-saving-photos.md | 2 +- 1 file changed, 1 insertion(+), 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 6fb10b55485..7fa4250055b 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -51,7 +51,7 @@ import { Preferences } from '@capacitor/preferences'; }) export class PhotoService { public photos: UserPhoto[] = []; -dd + public async addNewToGallery() { // Take a photo const capturedPhoto = await Camera.getPhoto({ From 8cbe764f1e9836e86c4325e335968bd0dfa4ce9a Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 23 Oct 2025 18:20:34 -0700 Subject: [PATCH 46/68] docs(angular): update adding mobile page --- .../angular/your-first-app/5-adding-mobile.md | 130 +++++++++--------- 1 file changed, 62 insertions(+), 68 deletions(-) diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 810095f917a..1aa60967f55 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -1,4 +1,5 @@ --- +title: Adding Mobile strip_number_prefixes: false --- @@ -10,11 +11,11 @@ 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](../platform.md) 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: +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 +```ts import { Injectable } from '@angular/core'; import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; import { Filesystem, Directory } from '@capacitor/filesystem'; @@ -24,6 +25,7 @@ import { Platform } from '@ionic/angular'; export class PhotoService { public photos: UserPhoto[] = []; + private PHOTO_STORAGE: string = 'photos'; // CHANGE: Add a property to track the app's running platform. @@ -34,18 +36,19 @@ export class PhotoService { this.platform = platform; } - // other code + // Same old code from before. } ``` ## 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`'s' `readFile()` method. Otherwise, use the same logic as before when running the app on the web. Update `readAsBase64()` to look like the following: -```tsx +```ts private async readAsBase64(photo: Photo) { + // CHANGE: Add platform check. // "hybrid" will detect Cordova or Capacitor if (this.platform.is('hybrid')) { // Read the file into base64 format @@ -54,8 +57,7 @@ private async readAsBase64(photo: Photo) { }); 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(); @@ -65,7 +67,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 into `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](../../core-concepts/webview.md#file-protocol)). To use this method, we'll need to import Capacitor into `photo.service.ts`. ```tsx import { Capacitor } from '@capacitor/core'; @@ -73,42 +75,35 @@ 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) { - // 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 - }); +```ts +// CHANGE: Update `loadSaved` method. +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[]; - 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 - }; + // 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}`; + } } } ``` -Next, head back over to the `loadSaved()` function we implemented for the web earlier. On mobile, we can directly set the source of an image tag - `` - to each photo file on the Filesystem, displaying them automatically. Thus, only the web requires reading each image from the Filesystem into base64 format. Update this function to add an _if statement_ around the Filesystem code: +Next, head back over to the `loadSaved()` method we implemented for the web earlier. On mobile, we can directly set the source of an image tag - `` - to each photo file on the `Filesystem`, displaying them automatically. Thus, only the web requires reading each image from the `Filesystem` into base64 format. Update this method to add an _if statement_ around the `Filesystem` code: -```tsx +```ts +// CHANGE: Update `loadSaved` method. public async loadSaved() { // Retrieve cached photo array data const { value } = await Preferences.get({ key: this.PHOTO_STORAGE }); @@ -136,7 +131,7 @@ Our Photo Gallery now consists of one codebase that runs on the web, Android, an `photos.service.ts` should now look like this: -```tsx +```ts import { Injectable } from '@angular/core'; import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; import { Filesystem, Directory } from '@capacitor/filesystem'; @@ -149,7 +144,9 @@ import { Capacitor } from '@capacitor/core'; }) export class PhotoService { public photos: UserPhoto[] = []; + private PHOTO_STORAGE: string = 'photos'; + private platform: Platform; constructor(platform: Platform) { @@ -164,41 +161,16 @@ export class PhotoService { 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); @@ -213,7 +185,6 @@ export class PhotoService { 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), @@ -246,8 +217,8 @@ export class PhotoService { } } - private convertBlobToBase64 = (blob: Blob) => - new Promise((resolve, reject) => { + private convertBlobToBase64(blob: Blob) { + return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = reject; reader.onload = () => { @@ -255,6 +226,29 @@ export class PhotoService { }; reader.readAsDataURL(blob); }); + } + + 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}`; + } + } + } } export interface UserPhoto { From 77848e59ac2e6495b88489ae0f72f325cd9ca413 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Fri, 24 Oct 2025 12:04:40 -0700 Subject: [PATCH 47/68] docs(angular): use correct method --- .../angular/your-first-app/5-adding-mobile.md | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 1aa60967f55..69fd2c9dcc6 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -69,33 +69,39 @@ 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](../../core-concepts/webview.md#file-protocol)). To use this method, we'll need to import Capacitor into `photo.service.ts`. -```tsx +```ts import { Capacitor } from '@capacitor/core'; ``` Then update `savePicture()` to look like the following: ```ts -// CHANGE: Update `loadSaved` method. -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 - }); +// CHANGE: Update `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 + }); - // Web platform only: Load the photo as base64 data - photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; - } + if (this.platform.is('hybrid')) { + // Display the new image by rewriting the 'file://' path to HTTP + 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 + }; } } ``` From 7714f0e04958d3affad1e36ae68cb4e7d926e0f5 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Fri, 24 Oct 2025 12:38:03 -0700 Subject: [PATCH 48/68] docs(angular): update live reload page --- docs/angular/your-first-app/7-live-reload.md | 194 +++++++------------ 1 file changed, 71 insertions(+), 123 deletions(-) diff --git a/docs/angular/your-first-app/7-live-reload.md b/docs/angular/your-first-app/7-live-reload.md index 7cdc8dd82de..5f63225993a 100644 --- a/docs/angular/your-first-app/7-live-reload.md +++ b/docs/angular/your-first-app/7-live-reload.md @@ -12,7 +12,7 @@ sidebar_label: Live Reload So far, we’ve seen how easy it is to develop a cross-platform app that works everywhere. The development experience is pretty quick, but what if I told you there was a way to go faster? -We can use the Ionic CLI’s [Live Reload functionality](https://ionicframework.com/docs/cli/livereload) to boost our productivity when building Ionic apps. When active, Live Reload will reload the browser and/or WebView when changes in the app are detected. +We can use the Ionic CLI’s [Live Reload functionality](../../cli/livereload.md) to boost our productivity when building Ionic apps. When active, Live Reload will reload the browser and/or WebView when changes in the app are detected. ## Live Reload @@ -23,76 +23,66 @@ We can also use it when developing on iOS and Android devices. This is particula Let’s use Live Reload to implement photo deletion, the missing piece of our Photo Gallery feature. Select your platform of choice (iOS or Android) and connect a device to your computer. Next, run either command in a terminal, based on your chosen platform: ```shell -$ ionic cap run ios -l --external +ionic cap run ios -l --external -$ ionic cap run android -l --external +ionic cap run android -l --external ``` The Live Reload server will start up, and the native IDE of choice will open if not opened already. Within the IDE, click the Play button to launch the app onto your device. ## Deleting Photos -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. +With Live Reload running and the app open on your device, let’s implement photo deletion functionality. -```html - - - Photo Gallery - - +In `photo.service.ts`, add the `deletePicture()` method. The selected photo is removed from the `photos` array first. Then, we use the Capacitor Preferences API to update the cached version of the `photos` array. Finally, we delete the actual photo file itself using the Filesystem API. - - - - Photo Gallery - - +```ts +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 { + // Same old code from before. - - - - - - -``` + // CHANGE: Add `deletePicture()` method. + public async deletePicture(photo: UserPhoto, position: number) { + // Remove this photo from the Photos reference data array + this.photos.splice(position, 1); -Over in `tab2.page.ts`, import `ActionSheetController` and add it to the constructor: + // Update photos array cache by overwriting the existing photo array + Preferences.set({ + key: this.PHOTO_STORAGE, + value: JSON.stringify(this.photos), + }); -```tsx -import { Component } from '@angular/core'; -import { PhotoService } from '../services/photo.service'; -// CHANGE: Add import. -import { ActionSheetController } from '@ionic/angular'; + // Delete photo file from filesystem + const filename = photo.filepath.substr(photo.filepath.lastIndexOf('/') + 1); -@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) {} + await Filesystem.deleteFile({ + path: filename, + directory: Directory.Data, + }); + } +} - // other code +export interface UserPhoto { + filepath: string; + webviewPath?: string; } ``` -Add `UserPhoto` to the import statement. +Next, in `tab2.page.ts`, implement the `showActionSheet()` method. We're adding two options: "Delete", which calls `PhotoService.deletePicture()`, and "Cancel". The cancel button will automatically closes the action sheet when assigned the "cancel" role. -```tsx +```ts import { Component } from '@angular/core'; -// CHANGE: Update import. -import { PhotoService, UserPhoto } from '../services/photo.service'; +import { PhotoService } from '../services/photo.service'; +// CHANGE: Add import. import { ActionSheetController } from '@ionic/angular'; @Component({ @@ -102,29 +92,12 @@ import { ActionSheetController } from '@ionic/angular'; standalone: false, }) export class Tab2Page { + // CHANGE: Update constructor. 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 -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 { // Same old code from before. - // CHANGE: Add `showActionSheet` function. + // CHANGE: Add `showActionSheet` method. public async showActionSheet(photo: UserPhoto, position: number) { const actionSheet = await this.actionSheetController.create({ header: 'Photos', @@ -152,64 +125,39 @@ export class Tab2Page { } ``` -Save both of the files we just edited. The Photo Gallery app will reload automatically, and now when we tap on one of the photos in the gallery, the action sheet displays. Tapping “Delete” doesn’t do anything yet, so head back into your code editor. - -In `src/app/services/photo.service.ts`, add the `deletePicture()` function: - -```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 { - // 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); +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](../../api/action-sheet.md) dialog with the option to either delete the selected photo or cancel (close) the dialog. - // 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, - }); - } +```html + + + Photo Gallery + + - private async readAsBase64(photo: Photo) { - // Same old code from before. - } + + + + Photo Gallery + + - // Same old code from before. -} + + + + + + + + -export interface UserPhoto { - filepath: string; - webviewPath?: string; -} + + + + + + ``` -The selected photo is removed from the Photos array first. Then, we use the Capacitor Preferences API to update the cached version of the Photos array. Finally, we delete the actual photo file itself using the Filesystem API. - -Save this file, then tap on a photo again and choose the “Delete” option. This time, the photo is deleted! Implemented much faster using Live Reload. 💪 +Tap on a photo again and choose the “Delete” option. The photo is deleted! Implemented much faster using Live Reload. 💪 In the final portion of this tutorial, we’ll walk you through the basics of the Appflow product used to build and deploy your application to users' devices. From dc6764c3658cbab9ff65447e92aa9547684ac038 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Fri, 24 Oct 2025 16:57:05 -0700 Subject: [PATCH 49/68] docs(angular): update the your first app pages --- docs/angular/your-first-app.md | 6 +++--- docs/angular/your-first-app/2-taking-photos.md | 10 +++------- docs/angular/your-first-app/3-saving-photos.md | 1 + docs/angular/your-first-app/5-adding-mobile.md | 4 ++-- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index a518093bc29..7b9af732ef8 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -34,9 +34,9 @@ We'll create a Photo Gallery app that offers the ability to take photos with you Highlights include: -- One Angular-based codebase that runs on the web, iOS, and Android using Ionic Framework [UI components](https://ionicframework.com/docs/components). +- One Angular-based codebase that runs on the web, iOS, and Android using Ionic Framework [UI components](../components.md). - Deployed as a native iOS and Android mobile app using [Capacitor](https://capacitorjs.com), Ionic's official native app runtime. -- Photo Gallery functionality powered by the Capacitor [Camera](https://capacitorjs.com/docs/apis/camera), [Filesystem](https://capacitorjs.com/docs/apis/filesystem), and [Preferences](https://capacitorjs.com/docs/apis/preferences) APIs. +- Photo Gallery functionality powered by the Capacitor [Camera](../native/camera.md), [Filesystem](../native/filesystem.md), and [Preferences](../native/preferences.md) APIs. Find the complete app code referenced in this guide [on GitHub](https://github.com/ionic-team/photo-gallery-capacitor-ng). @@ -114,7 +114,7 @@ import { AppModule } from './app/app.module'; // CHANGE: Add the following import. import { defineCustomElements } from '@ionic/pwa-elements/loader'; -// Call the element loader before the `bootstrapModule` call. +// CHANGE: Call the element loader before the `bootstrapModule` call. defineCustomElements(window); platformBrowserDynamic() diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index f3c40f8808a..91f0de0d397 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -11,7 +11,7 @@ sidebar_label: Taking Photos /> -Now for the fun part - adding the ability to take photos with the device’s camera using the Capacitor [Camera API](https://capacitorjs.com/docs/apis/camera). We’ll begin with building it for the web, then make some small tweaks to make it work on mobile (iOS and Android). +Now for the fun part - adding the ability to take photos with the device’s camera using the Capacitor [Camera API](../../native/camera.md). We’ll begin with building it for the web, then make some small tweaks to make it work on mobile (iOS and Android). ## Photo Service @@ -147,9 +147,10 @@ export class PhotoService { } ``` -Over in the `addNewToGallery` method, add the newly captured photo to the beginning of the Photos array. +Over in the `addNewToGallery` method, add the newly captured photo to the beginning of the `photos` array. ```ts +// CHANGE: Update `addNewToGallery()` method. public async addNewToGallery() { const capturedPhoto = await Camera.getPhoto({ resultType: CameraResultType.Uri, @@ -169,7 +170,6 @@ public async addNewToGallery() { ```ts 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'; @@ -178,10 +178,8 @@ import { Preferences } from '@capacitor/preferences'; providedIn: 'root', }) export class PhotoService { - // CHANGE: Add the photos array. public photos: UserPhoto[] = []; - // CHANGE: Add the gallery function. public async addNewToGallery() { // Take a photo const capturedPhoto = await Camera.getPhoto({ @@ -190,7 +188,6 @@ export class PhotoService { quality: 100, }); - // CHANGE: Add the new photo to the photos array. this.photos.unshift({ filepath: 'soon...', webviewPath: capturedPhoto.webPath!, @@ -198,7 +195,6 @@ export class PhotoService { } } -// CHANGE: Add the interface. export interface UserPhoto { filepath: string; webviewPath?: string; diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index 7fa4250055b..18eb0f788df 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -52,6 +52,7 @@ import { Preferences } from '@capacitor/preferences'; export class PhotoService { public photos: UserPhoto[] = []; + // CHANGE: Update `addNewToGallery()` method. public async addNewToGallery() { // Take a photo const capturedPhoto = await Camera.getPhoto({ diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 69fd2c9dcc6..b7612a3b168 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -42,7 +42,7 @@ 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`'s' `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()` method, 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`'s' `readFile()` method. Otherwise, use the logic as before when running the app on the web. Update `readAsBase64()` to look like the following: @@ -106,7 +106,7 @@ private async savePicture(photo: Photo) { } ``` -Next, head back over to the `loadSaved()` method we implemented for the web earlier. On mobile, we can directly set the source of an image tag - `` - to each photo file on the `Filesystem`, displaying them automatically. Thus, only the web requires reading each image from the `Filesystem` into base64 format. Update this method to add an _if statement_ around the `Filesystem` code: +Next, add a new bit of logic in the `loadSaved()` method. On mobile, we can directly point to each photo file on the Filesystem and display them automatically. On the web, however, we must read each image from the Filesystem into base64 format. This is because the Filesystem API uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood. Update the `loadSaved()` method: ```ts // CHANGE: Update `loadSaved` method. From 49210d3d56d1e50c43332e597c09b528bd491a8e Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 28 Oct 2025 16:42:19 -0700 Subject: [PATCH 50/68] docs(angular): update your first app pages --- docs/angular/your-first-app.md | 12 +- .../angular/your-first-app/2-taking-photos.md | 15 ++- .../angular/your-first-app/3-saving-photos.md | 53 ++++---- .../your-first-app/4-loading-photos.md | 40 +++--- .../angular/your-first-app/5-adding-mobile.md | 126 +++++++++++------- .../your-first-app/6-deploying-mobile.md | 22 +-- docs/angular/your-first-app/7-live-reload.md | 15 ++- docs/angular/your-first-app/8-distribute.md | 13 +- 8 files changed, 170 insertions(+), 126 deletions(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 7b9af732ef8..028b92151be 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -4,13 +4,15 @@ sidebar_label: Build Your First App --- - Build Your First Ionic Mobile App: Angular Development Tutorial + Build Your First Ionic Mobile App with Angular | Ionic Capacitor Camera +# Your First Ionic App: Angular + The great thing about Ionic is that with one codebase, you can build for any platform using just HTML, CSS, and JavaScript. Follow along as we learn the fundamentals of Ionic app development by creating a realistic app step by step. Here’s the finished app running on all 3 platforms: @@ -38,7 +40,7 @@ Highlights include: - Deployed as a native iOS and Android mobile app using [Capacitor](https://capacitorjs.com), Ionic's official native app runtime. - Photo Gallery functionality powered by the Capacitor [Camera](../native/camera.md), [Filesystem](../native/filesystem.md), and [Preferences](../native/preferences.md) APIs. -Find the complete app code referenced in this guide [on GitHub](https://github.com/ionic-team/photo-gallery-capacitor-ng). +Find the [complete app code](https://github.com/ionic-team/tutorial-photo-gallery-angular) referenced in this guide on GitHub. ## Download Required Tools @@ -70,7 +72,7 @@ Consider setting up npm to operate globally without elevated permissions. See [R ## Create an App -Next, create an Ionic Angular app that uses the “Tabs” starter template and adds Capacitor for native functionality: +Next, create an Ionic Angular app that uses the "Tabs" starter template and adds Capacitor for native functionality: ```shell ionic start photo-gallery tabs --type=angular --capacitor @@ -194,7 +196,7 @@ We put the visual aspects of our app into ``. In this case, it’s ``` -Next, open `src/app/tabs/tabs.page.html`. Change the label to “Photos” and the `ellipse` icon to `images` for the middle tab button. +Next, open `src/app/tabs/tabs.page.html`. Change the label to "Photos" and the `ellipse` icon to `images` for the middle tab button. ```html diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 91f0de0d397..d888ac6d94b 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -4,13 +4,15 @@ sidebar_label: Taking Photos --- - Build Camera API for iOS, Android & Web | Ionic Capacitor Camera + Take Photos with Camera API for iOS, Android & Web with Angular | Ionic Capacitor Camera +# Taking Photos with the Camera + Now for the fun part - adding the ability to take photos with the device’s camera using the Capacitor [Camera API](../../native/camera.md). We’ll begin with building it for the web, then make some small tweaks to make it work on mobile (iOS and Android). ## Photo Service @@ -21,7 +23,7 @@ All Capacitor logic (Camera usage and other native features) will be encapsulate ionic g service services/photo.service ``` -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: +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: ```ts import { Injectable } from '@angular/core'; @@ -36,7 +38,7 @@ import { Preferences } from '@capacitor/preferences'; export class PhotoService {} ``` -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: +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. ```ts import { Injectable } from '@angular/core'; @@ -57,7 +59,7 @@ export class PhotoService { } ``` -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. +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 - `getPhoto()` - that will open up the device's camera and allow us to take photos. Next, in `tab2.page.ts`, import the `PhotoService` class and add a method to call its `addNewToGallery` method. @@ -127,7 +129,7 @@ export class PhotoService { // Same old code from before. } -// CHANGE: Add the interface. +// CHANGE: Add the `UserPhoto` interface. export interface UserPhoto { filepath: string; webviewPath?: string; @@ -138,7 +140,7 @@ Above the `addNewToGallery()` method, define an array of `UserPhoto`, which will ```ts export class PhotoService { - // CHANGE: Add the photos array. + // CHANGE: Add the `photos` array. public photos: UserPhoto[] = []; public async addNewToGallery() { @@ -152,6 +154,7 @@ Over in the `addNewToGallery` method, add the newly captured photo to the beginn ```ts // CHANGE: Update `addNewToGallery()` method. public async addNewToGallery() { + // Take a photo const capturedPhoto = await Camera.getPhoto({ resultType: CameraResultType.Uri, source: CameraSource.Camera, diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index 18eb0f788df..ccf3e047075 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -3,6 +3,14 @@ title: Saving Photos to the Filesystem sidebar_label: Saving Photos --- + + Saving Photos to the Filesystem with Angular | Ionic Capacitor Camera + + + # Saving Photos to the Filesystem We’re now able to take multiple photos and display them in a photo gallery on the second tab of our app. These photos, however, are not currently being stored permanently, so when the app is closed, they will be deleted. @@ -23,7 +31,7 @@ import { Preferences } from '@capacitor/preferences'; export class PhotoService { // Same old code from before. - // CHANGE: Add the `savePicture` method. + // CHANGE: Add the `savePicture()` method. private async savePicture(photo: Photo) { return { filepath: 'soon...', @@ -52,7 +60,7 @@ import { Preferences } from '@capacitor/preferences'; export class PhotoService { public photos: UserPhoto[] = []; - // CHANGE: Update `addNewToGallery()` method. + // CHANGE: Update the `addNewToGallery()` method. public async addNewToGallery() { // Take a photo const capturedPhoto = await Camera.getPhoto({ @@ -61,7 +69,7 @@ export class PhotoService { quality: 100, }); - // CHANGE: Add `savedImageFile` to save the picture and add it to photo collection. + // CHANGE: Add `savedImageFile`. // Save the picture and add it to photo collection const savedImageFile = await this.savePicture(capturedPhoto); @@ -83,11 +91,11 @@ export interface UserPhoto { } ``` -We'll use the Capacitor [Filesystem API](../../native/filesystem.md) to save the photo. First, convert the photo to base64 format using a helper method we'll define: `readAsBase64()`. +We'll use the Capacitor [Filesystem API](../../native/filesystem.md) to save the photo. First, convert the photo to base64 format. Then, pass the data to the Filesystem's `writeFile` method. Recall that we display photos by setting the image's source path (`src`) to the `webviewPath` property. So, set the `webviewPath` and return the new `Photo` object. -The `readAsBase64()` method is necessary because it isolates a small amount of platform-specific logic (more on that soon). For now, create two new helper methods, `readAsBase64()` and `convertBlobToBase64()`, to implement the necessary logic for running on the web. +For now, create a new helper method, `convertBlobToBase64()`, to implement the necessary logic for running on the web. ```ts import { Injectable } from '@angular/core'; @@ -101,10 +109,12 @@ import { Preferences } from '@capacitor/preferences'; export class PhotoService { // Same old code from before. - // CHANGE: Update the `savePicture` method. + // 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); + // Fetch the photo, read as a blob, then convert to base64 format + const response = await fetch(photo.webPath!); + const blob = await response.blob(); + const base64Data = (await this.convertBlobToBase64(blob)) as string; // Write the file to the data directory const fileName = Date.now() + '.jpeg'; @@ -122,15 +132,6 @@ export class PhotoService { }; } - // 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) { return new Promise((resolve, reject) => { @@ -179,8 +180,10 @@ export class PhotoService { } private async savePicture(photo: Photo) { - // Convert photo to base64 format, required by Filesystem API to save - const base64Data = await this.readAsBase64(photo); + // Fetch the photo, read as a blob, then convert to base64 format + const response = await fetch(photo.webPath!); + const blob = await response.blob(); + const base64Data = (await this.convertBlobToBase64(blob)) as string; // Write the file to the data directory const fileName = Date.now() + '.jpeg'; @@ -198,14 +201,6 @@ export class PhotoService { }; } - 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) { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -226,6 +221,4 @@ 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. diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index 372bcea4432..2965d477e65 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -3,6 +3,14 @@ title: Loading Photos from the Filesystem sidebar_label: Loading Photos --- + + Loading Photos from the Filesystem with Angular | Ionic Capacitor Camera + + + # Loading Photos from the Filesystem We’ve implemented photo taking and saving to the filesystem. There’s one last piece of functionality missing: the photos are stored in the filesystem, but we need a way to save pointers to each file so that they can be displayed again in the photo gallery. @@ -11,7 +19,7 @@ Fortunately, this is easy: we’ll leverage the Capacitor [Preferences API](../. ## Preferences API -Open `photo.service.ts` and begin by defining a new property in the `PhotoService` class 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. ```ts export class PhotoService { @@ -24,7 +32,7 @@ export class PhotoService { } ``` -Next, at the end of the `addNewToGallery` method, 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. +Next, at the end of the `addNewToGallery()` method, 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. ```ts public async addNewToGallery() { @@ -56,8 +64,8 @@ export class PhotoService { // 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 }); - this.photos = (value ? JSON.parse(value) : []) as UserPhoto[]; + const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; } } ``` @@ -71,13 +79,13 @@ export class PhotoService { // CHANGE: Update the `loadSaved` method. 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[]; + const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; // 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({ + const file = await Filesystem.file({ path: photo.filepath, directory: Directory.Data, }); @@ -124,8 +132,10 @@ export class PhotoService { } private async savePicture(photo: Photo) { - // Convert photo to base64 format, required by Filesystem API to save - const base64Data = await this.readAsBase64(photo); + // Fetch the photo, read as a blob, then convert to base64 format + const response = await fetch(photo.webPath!); + const blob = await response.blob(); + const base64Data = (await this.convertBlobToBase64(blob)) as string; // Write the file to the data directory const fileName = Date.now() + '.jpeg'; @@ -143,14 +153,6 @@ export class PhotoService { }; } - 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) { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -164,8 +166,8 @@ export class PhotoService { 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[]; + const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; } } diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index b7612a3b168..25c10cb4cdf 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -3,6 +3,14 @@ title: Adding Mobile strip_number_prefixes: false --- + + Adding Mobile Support with Angular | Ionic Capacitor Camera + + + # Adding Mobile Our photo gallery app won’t be complete until it runs on iOS, Android, and the web - all using one codebase. All it takes is some small logic changes to support mobile platforms, installing some native tooling, then running the app on a device. Let’s go! @@ -42,12 +50,15 @@ export class PhotoService { ## Platform-specific Logic -First, we’ll update the photo saving functionality to support mobile. In the `readAsBase64()` method, 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`'s' `readFile()` method. Otherwise, use the logic as before when running the app on the web. +First, we’ll update the photo saving functionality to support mobile. In the `savePicture()` method, check which platform the app is running on. If it’s “hybrid” (Capacitor, the native runtime), then read the photo file into base64 format using the `Filesystem`'s' `readFile()` method. Otherwise, use the same logic as before when running the app on the web. -Update `readAsBase64()` to look like the following: +Update `savePicture()` to look like the following: ```ts -private async readAsBase64(photo: Photo) { +// CHANGE: Update the `savePicture()` method. +private async savePicture(photo: Photo) { + let base64Data: string | Blob; + // CHANGE: Add platform check. // "hybrid" will detect Cordova or Capacitor if (this.platform.is('hybrid')) { @@ -55,19 +66,32 @@ private async readAsBase64(photo: Photo) { const file = await Filesystem.readFile({ path: photo.path! }); - - return file.data; + base64Data = 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; + base64Data = await this.convertBlobToBase64(blob) as string; } + + // 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, + }; } ``` -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](../../core-concepts/webview.md#file-protocol)). To use this method, we'll need to import Capacitor into `photo.service.ts`. +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](../../core-concepts/webview.md#file-protocol)). To use this method, we'll need to import Capacitor into `photo.service.ts`. ```ts import { Capacitor } from '@capacitor/core'; @@ -78,17 +102,29 @@ Then update `savePicture()` to look like the following: ```ts // CHANGE: Update `savePicture()` method. private async savePicture(photo: Photo) { - // Convert photo to base64 format, required by Filesystem API to save - const base64Data = await this.readAsBase64(photo); + let base64Data: string | Blob; + // "hybrid" will detect mobile - iOS or Android + if (this.platform.is('hybrid')) { + const file = await Filesystem.readFile({ + path: photo.path!, + }); + base64Data = 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(); + base64Data = await this.convertBlobToBase64(blob) as string; + } // Write the file to the data directory const fileName = Date.now() + '.jpeg'; const savedFile = await Filesystem.writeFile({ path: fileName, data: base64Data, - directory: Directory.Data + directory: Directory.Data, }); + // CHANGE: Add platform check. if (this.platform.is('hybrid')) { // Display the new image by rewriting the 'file://' path to HTTP return { @@ -100,7 +136,7 @@ private async savePicture(photo: Photo) { // already loaded into memory return { filepath: fileName, - webviewPath: photo.webPath + webviewPath: photo.webPath, }; } } @@ -111,23 +147,20 @@ Next, add a new bit of logic in the `loadSaved()` method. On mobile, we can dire ```ts // CHANGE: Update `loadSaved` method. 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[]; + const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; - // Easiest way to detect when running on the web: - // “when the platform is NOT hybrid, do this” + // CHANGE: Add platform check. + // If running on the web... 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({ + const file = await Filesystem.file({ path: photo.filepath, directory: Directory.Data }); // Web platform only: Load the photo as base64 data - photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; + photo.webviewPath = `data:image/jpeg;base64,${file.data}`; } } } @@ -178,8 +211,23 @@ export class PhotoService { } private async savePicture(photo: Photo) { - // Convert photo to base64 format, required by Filesystem API to save - const base64Data = await this.readAsBase64(photo); + let base64Data: string | Blob; + + // "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!, + }); + + base64Data = 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(); + + base64Data = (await this.convertBlobToBase64(blob)) as string; + } // Write the file to the data directory const fileName = Date.now() + '.jpeg'; @@ -205,24 +253,6 @@ export class PhotoService { } } - 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) { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -236,22 +266,18 @@ export class PhotoService { 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[]; + const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; - // Easiest way to detect when running on the web: - // “when the platform is NOT hybrid, do this” + // If running on the web... 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({ + const file = await Filesystem.file({ path: photo.filepath, directory: Directory.Data, }); - // Web platform only: Load the photo as base64 data - photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; + photo.webviewPath = `data:image/jpeg;base64,${file.data}`; } } } diff --git a/docs/angular/your-first-app/6-deploying-mobile.md b/docs/angular/your-first-app/6-deploying-mobile.md index e9637084b7b..3497cad7fba 100644 --- a/docs/angular/your-first-app/6-deploying-mobile.md +++ b/docs/angular/your-first-app/6-deploying-mobile.md @@ -4,20 +4,24 @@ sidebar_label: Deploying Mobile --- - Deploying to iOS and Android Apps - Capacitor Setup on Ionic + Adding Mobile Support with Angular | Ionic Capacitor Camera -Since we added Capacitor to our project when it was first created, there’s only a handful of steps remaining until the Photo Gallery app is on our device! Remember, you can find the complete source code for this app [here](https://github.com/ionic-team/photo-gallery-capacitor-ng). +Since we added Capacitor to our project when it was first created, there’s only a handful of steps remaining until the Photo Gallery app is on our device! + +:::note +Remember, you can find the complete source code for this app [here](https://github.com/ionic-team/photo-gallery-capacitor-ng). +::: ## Capacitor Setup Capacitor is Ionic’s official app runtime that makes it easy to deploy web apps to native platforms like iOS, Android, and more. If you’ve used Cordova in the past, consider reading more about the differences [here](https://capacitorjs.com/docs/cordova#differences-between-capacitor-and-cordova). -If you’re still running `ionic serve` in the terminal, cancel it. Complete a fresh build of your Ionic project, fixing any errors that it reports: +If you’re still running `ionic serve` in the terminal, cancel it. Complete a fresh build of the Ionic project, fixing any errors that it reports: ```shell ionic build @@ -26,8 +30,8 @@ ionic build Next, create both the iOS and Android projects: ```shell -$ ionic cap add ios -$ ionic cap add android +ionic cap add ios +ionic cap add android ``` Both android and ios folders at the root of the project are created. These are entirely standalone native projects that should be considered part of your Ionic app (i.e., check them into source control, edit them using their native tooling, etc.). @@ -44,7 +48,7 @@ Note: After making updates to the native portion of the code (such as adding a n ionic cap sync ``` -## iOS Deployment +## iOS :::note To build an iOS app, you’ll need a Mac computer. @@ -58,7 +62,7 @@ First, run the Capacitor `open` command, which opens the native iOS project in X ionic cap open ios ``` -In order for some native plugins to work, user permissions must be configured. In our photo gallery app, this includes the Camera plugin: iOS displays a modal dialog automatically after the first time that `Camera.getPhoto()` is called, prompting the user to allow the app to use the Camera. The permission that drives this is labeled “Privacy - Camera Usage.” To set it, the `Info.plist` file must be modified ([more details here](https://capacitorjs.com/docs/ios/configuration)). To access it, click "Info," then expand "Custom iOS Target Properties." +In order for some native plugins to work, user permissions must be configured. In our photo gallery app, this includes the Camera plugin: iOS displays a modal dialog automatically after the first time that `Camera.getPhoto()` is called, prompting the user to allow the app to use the Camera. The permission that drives this is labeled "Privacy - Camera Usage." To set it, the `Info.plist` file must be modified ([more details here](https://capacitorjs.com/docs/ios/configuration)). To access it, click "Info," then expand "Custom iOS Target Properties." ![The Info.plist file in Xcode showing the NSCameraUsageDescription key added for camera access.](/img/guides/first-app-cap-ng/xcode-info-plist.png 'Xcode Info.plist Configuration') @@ -80,7 +84,7 @@ Upon tapping the Camera button on the Photo Gallery tab, the permission prompt w ![Two iPhones side by side, one showing the camera permission prompt and the other displaying a photo taken with the app.](/img/guides/first-app-cap-ng/ios-permissions-photo.png 'iOS Camera Permission Prompt and Photo Result') -## Android Deployment +## Android Capacitor Android apps are configured and managed through Android Studio. Before running this app on an Android device, there's a couple of steps to complete. @@ -111,4 +115,4 @@ Once again, upon tapping the Camera button on the Photo Gallery tab, the permiss Our Photo Gallery app has just been deployed to Android and iOS devices. 🎉 -In the next portion of this tutorial, we’ll use the Ionic CLI’s Live Reload functionality to quickly implement photo deletion - thus completing our Photo Gallery feature. +In the final portion of this tutorial, we’ll use the Ionic CLI’s Live Reload functionality to quickly implement photo deletion - thus completing our Photo Gallery feature. diff --git a/docs/angular/your-first-app/7-live-reload.md b/docs/angular/your-first-app/7-live-reload.md index 5f63225993a..751f6cf0eaa 100644 --- a/docs/angular/your-first-app/7-live-reload.md +++ b/docs/angular/your-first-app/7-live-reload.md @@ -4,12 +4,15 @@ sidebar_label: Live Reload --- + Rapid App Development with Live Reload with Angular | Ionic Capacitor Camera +# Rapid App Development with Live Reload + So far, we’ve seen how easy it is to develop a cross-platform app that works everywhere. The development experience is pretty quick, but what if I told you there was a way to go faster? We can use the Ionic CLI’s [Live Reload functionality](../../cli/livereload.md) to boost our productivity when building Ionic apps. When active, Live Reload will reload the browser and/or WebView when changes in the app are detected. @@ -34,7 +37,7 @@ 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. -In `photo.service.ts`, add the `deletePicture()` method. The selected photo is removed from the `photos` array first. Then, we use the Capacitor Preferences API to update the cached version of the `photos` array. Finally, we delete the actual photo file itself using the Filesystem API. +In `photo.service.ts`, add the `deletePhoto()` method. The selected photo is removed from the `photos` array first. Then, we use the Capacitor Preferences API to update the cached version of the `photos` array. Finally, we delete the actual photo file itself using the Filesystem API. ```ts import { Injectable } from '@angular/core'; @@ -50,8 +53,8 @@ import { Capacitor } from '@capacitor/core'; export class PhotoService { // Same old code from before. - // CHANGE: Add `deletePicture()` method. - public async deletePicture(photo: UserPhoto, position: number) { + // CHANGE: Add `deletePhoto()` method. + public async deletePhoto(photo: UserPhoto, position: number) { // Remove this photo from the Photos reference data array this.photos.splice(position, 1); @@ -77,10 +80,12 @@ export interface UserPhoto { } ``` -Next, in `tab2.page.ts`, implement the `showActionSheet()` method. We're adding two options: "Delete", which calls `PhotoService.deletePicture()`, and "Cancel". The cancel button will automatically closes the action sheet when assigned the "cancel" role. +Next, in `tab2.page.ts`, implement the `showActionSheet()` method. We're adding two options: "Delete", which calls `PhotoService.deletePhoto()`, and "Cancel". The cancel button will automatically closes the action sheet when assigned the "cancel" role. ```ts import { Component } from '@angular/core'; +// Change: Add import. +import type { UserPhoto } from '../services/photo.service'; import { PhotoService } from '../services/photo.service'; // CHANGE: Add import. import { ActionSheetController } from '@ionic/angular'; @@ -107,7 +112,7 @@ export class Tab2Page { role: 'destructive', icon: 'trash', handler: () => { - this.photoService.deletePicture(photo, position); + this.photoService.deletePhoto(photo, position); }, }, { diff --git a/docs/angular/your-first-app/8-distribute.md b/docs/angular/your-first-app/8-distribute.md index 65ef475f6be..51db7df24ed 100644 --- a/docs/angular/your-first-app/8-distribute.md +++ b/docs/angular/your-first-app/8-distribute.md @@ -1,7 +1,16 @@ --- +title: Build and Distribute your App sidebar_label: Distribute --- + + Build and Deploy your App with Angular | Ionic Capacitor Camera + + + # Build and Deploy your App Now that you have built your first app, you are going to want to get it distributed so everyone can start using it. The mechanics of building and deploying your application can be quite cumbersome. That is where [Appflow](https://ionic.io/docs/appflow/) comes into play. Appflow allows you to effectively generate web and native builds, push out live app updates, publish your app to the app stores, and automate the whole process. The entire Quickstart guide can be found [here](https://ionic.io/docs/appflow/quickstart). @@ -52,7 +61,7 @@ Upon completion of the Web Build, additional versioning options are available to To receive this live update, you will need to run the app on a device or an emulator. The quickest and easiest way to do this is through the following command: ```shell -ionic [cordova | cap] run [ios | android] [options] +ionic cordova run [ios | android] [options] ``` Assuming the app is configured correctly to listen to the channel you deployed to, the app should immediately update on startup if you have chosen the auto update method during setup. If the background update method was chosen, be sure to stay in the app for about 30 seconds to ensure the update was downloaded. Then, close the application, reopen it, and you will see the updates applied! @@ -95,6 +104,6 @@ For access to the ability to create a Native Configuration, you will need to be Congratulations! You developed a complete cross-platform Photo Gallery app that runs on the web, iOS, and Android. Not only that, you have also then built the app and deployed it to your users devices! -There are many paths to follow from here. Try adding another [Ionic UI component](https://ionicframework.com/docs/components) to the app, or more [native functionality](https://capacitorjs.com/docs/apis). The sky’s the limit. Once you have added another feature, run the build and deploy process again through Appflow to get it out to your users. +There are many paths to follow from here. Try adding another [Ionic UI component](../../components.md) to the app, or more [native functionality](https://capacitorjs.com/docs/apis). The sky’s the limit. Once you have added another feature, run the build and deploy process again through Appflow to get it out to your users. Happy app building! 💙 From a4e4db66adbacc404d8387574d478f2d54adda07 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 28 Oct 2025 16:54:50 -0700 Subject: [PATCH 51/68] Correct typo in deployment message Fixed typo in the congratulations message regarding user devices. --- docs/angular/your-first-app/8-distribute.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/8-distribute.md b/docs/angular/your-first-app/8-distribute.md index 51db7df24ed..195ef764561 100644 --- a/docs/angular/your-first-app/8-distribute.md +++ b/docs/angular/your-first-app/8-distribute.md @@ -102,7 +102,7 @@ For access to the ability to create a Native Configuration, you will need to be ## What’s Next? -Congratulations! You developed a complete cross-platform Photo Gallery app that runs on the web, iOS, and Android. Not only that, you have also then built the app and deployed it to your users devices! +Congratulations! You developed a complete cross-platform Photo Gallery app that runs on the web, iOS, and Android. Not only that, you have also then built the app and deployed it to your users' devices! There are many paths to follow from here. Try adding another [Ionic UI component](../../components.md) to the app, or more [native functionality](https://capacitorjs.com/docs/apis). The sky’s the limit. Once you have added another feature, run the build and deploy process again through Appflow to get it out to your users. From c3a34a9796fce244277a383d585c0e893f9dc882 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 29 Oct 2025 12:50:29 -0700 Subject: [PATCH 52/68] docs(angular): update your first app pages for v7 --- .../version-v7/angular/your-first-app.md | 102 +++++-- .../angular/your-first-app/2-taking-photos.md | 177 +++++++++--- .../angular/your-first-app/3-saving-photos.md | 253 ++++++++++++++---- .../your-first-app/4-loading-photos.md | 209 ++++++++++++--- .../angular/your-first-app/5-adding-mobile.md | 243 ++++++++++++++--- .../your-first-app/6-deploying-mobile.md | 22 +- .../angular/your-first-app/7-live-reload.md | 192 ++++++++----- .../angular/your-first-app/8-distribute.md | 15 +- 8 files changed, 950 insertions(+), 263 deletions(-) diff --git a/versioned_docs/version-v7/angular/your-first-app.md b/versioned_docs/version-v7/angular/your-first-app.md index bfc88a1c477..028b92151be 100644 --- a/versioned_docs/version-v7/angular/your-first-app.md +++ b/versioned_docs/version-v7/angular/your-first-app.md @@ -4,13 +4,15 @@ sidebar_label: Build Your First App --- - Build Your First Ionic Mobile App: Angular Development Tutorial + Build Your First Ionic Mobile App with Angular | Ionic Capacitor Camera +# Your First Ionic App: Angular + The great thing about Ionic is that with one codebase, you can build for any platform using just HTML, CSS, and JavaScript. Follow along as we learn the fundamentals of Ionic app development by creating a realistic app step by step. Here’s the finished app running on all 3 platforms: @@ -19,9 +21,9 @@ Here’s the finished app running on all 3 platforms: width="560" height="315" src="https://www.youtube.com/embed/0ASQ13Y1Rk4" - frameborder="0" + frameBorder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" - allowfullscreen + allowFullScreen > :::note @@ -34,11 +36,11 @@ We'll create a Photo Gallery app that offers the ability to take photos with you Highlights include: -- One Angular-based codebase that runs on the web, iOS, and Android using Ionic Framework [UI components](https://ionicframework.com/docs/components). +- One Angular-based codebase that runs on the web, iOS, and Android using Ionic Framework [UI components](../components.md). - Deployed as a native iOS and Android mobile app using [Capacitor](https://capacitorjs.com), Ionic's official native app runtime. -- Photo Gallery functionality powered by the Capacitor [Camera](https://capacitorjs.com/docs/apis/camera), [Filesystem](https://capacitorjs.com/docs/apis/filesystem), and [Preferences](https://capacitorjs.com/docs/apis/preferences) APIs. +- Photo Gallery functionality powered by the Capacitor [Camera](../native/camera.md), [Filesystem](../native/filesystem.md), and [Preferences](../native/preferences.md) APIs. -Find the complete app code referenced in this guide [on GitHub](https://github.com/ionic-team/photo-gallery-capacitor-ng). +Find the [complete app code](https://github.com/ionic-team/tutorial-photo-gallery-angular) referenced in this guide on GitHub. ## Download Required Tools @@ -47,9 +49,8 @@ Download and install these right away to ensure an optimal Ionic development exp - **Node.js** for interacting with the Ionic ecosystem. [Download the LTS version here](https://nodejs.org/en/). - **A code editor** for... writing code! We are fans of [Visual Studio Code](https://code.visualstudio.com/). - **Command-line interface/terminal (CLI)**: - - **Windows** users: for the best Ionic experience, we recommend the built-in command line (cmd) or the Powershell - CLI, running in Administrator mode. - - **Mac/Linux** users, virtually any terminal will work. + - **Windows** users: for the best Ionic experience, we recommend the built-in command line (cmd) or the Powershell CLI, running in Administrator mode. + - **Mac/Linux** users: virtually any terminal will work. ## Install Ionic Tooling @@ -71,12 +72,18 @@ Consider setting up npm to operate globally without elevated permissions. See [R ## Create an App -Next, create an Ionic Angular app that uses the “Tabs” starter template and adds Capacitor for native functionality: +Next, create an Ionic Angular app that uses the "Tabs" starter template and adds Capacitor for native functionality: ```shell ionic start photo-gallery tabs --type=angular --capacitor ``` +:::note + +When prompted to choose between `NgModules` and `Standalone`, opt for `NgModules` as this tutorial follows the `NgModules` approach. + +::: + This starter project comes complete with three pre-built pages and best practices for Ionic development. With common building blocks already in place, we can add more features easily! Next, change into the app folder: @@ -93,7 +100,7 @@ npm install @capacitor/camera @capacitor/preferences @capacitor/filesystem ### PWA Elements -Some Capacitor plugins, including the Camera API, provide the web-based functionality and UI via the Ionic [PWA Elements library](https://github.com/ionic-team/ionic-pwa-elements). +Some Capacitor plugins, including the [Camera API](../native/camera.md), provide the web-based functionality and UI via the Ionic [PWA Elements library](https://github.com/ionic-team/pwa-elements). It's a separate dependency, so install it next: @@ -103,11 +110,18 @@ npm install @ionic/pwa-elements Next, import `@ionic/pwa-elements` by editing `src/main.ts`. -```tsx +```ts +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; +// CHANGE: Add the following import. import { defineCustomElements } from '@ionic/pwa-elements/loader'; -// Call the element loader before the bootstrapModule/bootstrapApplication call +// CHANGE: Call the element loader before the `bootstrapModule` 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. @@ -124,53 +138,87 @@ And voilà! Your Ionic app is now running in a web browser. Most of your app can ## Photo Gallery!!! -There are three tabs. Click on the Tab2 tab. It’s a blank canvas, aka the perfect spot to transform into a Photo Gallery. The Ionic CLI features Live Reload, so when you make changes and save them, the app is updated immediately! +There are three tabs. Click on the "Tab2" tab. It’s a blank canvas, aka the perfect spot to transform into a Photo Gallery. The Ionic CLI features Live Reload, so when you make changes and save them, the app is updated immediately! ![Animated GIF showing the live reload feature in an Ionic app, with changes in code immediately updating the app in a web browser.](/img/guides/first-app-cap-ng/email-photogallery.gif 'Live Reload Feature in Ionic App') -Open the photo-gallery app folder in your code editor of choice, then navigate to `/src/app/tab2/tab2.page.html`. We see: +Open `/src/app/tab2/tab2.page.html`. We see: ```html - + - Tab 2 + Tab 2 - + Tab 2 + + ``` -`ion-header` represents the top navigation and toolbar, with "Tab 2" as the title (there are two of them due to iOS [Collapsible Large Title](https://ionicframework.com/docs/api/title#collapsible-large-titles) support). Rename both `ion-title` elements to: +`ion-header` represents the top navigation and toolbar, with "Tab 2" as the title (there are two of them due to iOS [Collapsible Large Title](../api/title.md#collapsible-large-titles) support). Rename both `ion-title` elements to: ```html Photo Gallery ``` -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. +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](../api/fab.md) (FAB) to the bottom of the page and set the camera image as the icon. ```html + + + Photo Gallery + + + + + + Photo Gallery + + + + + + + ``` -Next, open `src/app/tabs/tabs.page.html`. Change the label to “Photos” and the icon name to “images”: +Next, open `src/app/tabs/tabs.page.html`. Change the label to "Photos" and the `ellipse` icon to `images` for the middle tab button. ```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. +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. diff --git a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md index 0e0cd078a70..d888ac6d94b 100644 --- a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md @@ -4,63 +4,105 @@ sidebar_label: Taking Photos --- - Build Camera API for iOS, Android & Web | Ionic Capacitor Camera + Take Photos with Camera API for iOS, Android & Web with Angular | Ionic Capacitor Camera -Now for the fun part - adding the ability to take photos with the device’s camera using the Capacitor [Camera API](https://capacitorjs.com/docs/apis/camera). We’ll begin with building it for the web, then make some small tweaks to make it work on mobile (iOS and Android). +# Taking Photos with the Camera + +Now for the fun part - adding the ability to take photos with the device’s camera using the Capacitor [Camera API](../../native/camera.md). We’ll begin with building it for the web, then make some small tweaks to make it work on mobile (iOS and Android). ## Photo Service All Capacitor logic (Camera usage and other native features) will be encapsulated in a service class. Create `PhotoService` using the `ionic generate` command: ```shell -ionic g service services/photo +ionic g service services/photo.service ``` -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: +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 +```ts +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 {} ``` -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: +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 - }); +```ts +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 { + // CHANGE: Add the gallery function. + public async addNewToGallery() { + // Take a photo + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + quality: 100, + }); + } } ``` -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. +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 - `getPhoto()` - that will open up the device's camera and allow us to take photos. -Next, open up `tab2.page.ts` and import the PhotoService class and add a method that calls the `addNewToGallery` method on the imported service: +Next, in `tab2.page.ts`, import the `PhotoService` class and add a method to call its `addNewToGallery` method. -```tsx +```ts +import { Component } from '@angular/core'; +// CHANGE: Import the PhotoService. 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 { + // CHANGE: Update constructor to include `photoService`. + constructor(public photoService: PhotoService) {} + + // CHANGE: Add `addNewToGallery` method. + addPhotoToGallery() { + this.photoService.addNewToGallery(); + } } ``` -Then, open `tab2.page.html` and call the `addPhotoToGallery()` function when the FAB is tapped/clicked: +Then, open `tab2.page.html` and call the `addPhotoToGallery()` method when the FAB is tapped/clicked: ```html - + + + Photo Gallery + + + + + + + Photo Gallery + + + + @@ -68,7 +110,7 @@ Then, open `tab2.page.html` and call the `addPhotoToGallery()` function when the ``` -Save the file, and if it's not running already, restart the development server in your browser by running `ionic serve`. On the Photo Gallery tab, click the Camera button. If your computer has a webcam of any sort, a modal window appears. Take a selfie! +If it's not running already, restart the development server in your browser by running `ionic serve`. On the Photo Gallery tab, click the Camera button. If your computer has a webcam of any sort, a modal window appears. Take a selfie! ![A photo gallery app displaying a webcam selfie.](/img/guides/first-app-cap-ng/camera-web.png 'Webcam Selfie in Photo Gallery') @@ -78,34 +120,48 @@ 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: -```tsx +```ts +export class PhotoService { + // Same old code from before. +} + +// CHANGE: Add the `UserPhoto` interface. export interface UserPhoto { filepath: string; webviewPath?: string; } ``` -Back at the top of the file, define an array of Photos, which will contain a reference to each photo captured with the Camera. +Above the `addNewToGallery()` method, define an array of `UserPhoto`, which will contain a reference to each photo captured with the Camera. -```tsx +```ts export class PhotoService { + // CHANGE: Add the `photos` array. public photos: UserPhoto[] = []; - // other code + public async addNewToGallery() { + // Same old code from before. + } } ``` -Over in the `addNewToGallery` function, add the newly captured photo to the beginning of the Photos array. +Over in the `addNewToGallery` method, add the newly captured photo to the beginning of the `photos` array. -```tsx +```ts +// CHANGE: Update `addNewToGallery()` method. +public async addNewToGallery() { + // Take a photo const capturedPhoto = await Camera.getPhoto({ resultType: CameraResultType.Uri, source: CameraSource.Camera, quality: 100 }); + // CHANGE: Add the new photo to the photos array. this.photos.unshift({ filepath: "soon...", webviewPath: capturedPhoto.webPath! @@ -113,22 +169,75 @@ Over in the `addNewToGallery` function, add the newly captured photo to the begi } ``` -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: +`photo.service.ts` should now look like this: + +```ts +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[] = []; + + public async addNewToGallery() { + // Take a photo + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + quality: 100, + }); + + this.photos.unshift({ + filepath: 'soon...', + webviewPath: capturedPhoto.webPath!, + }); + } +} + +export interface UserPhoto { + filepath: string; + webviewPath?: string; +} +``` + +Next, switch to `tab2.page.html` to display the images. We'll add a [Grid component](../../api/grid.md) to ensure the photos display neatly as they're added to the gallery. Inside the grid, loop through each photo in the `PhotoService`'s `photos` array. For each item, add an [Image component](../../api/img.md) and set its `src` property to the photo's path. ```html - + + + Photo Gallery + + + + + + + Photo Gallery + + + + + - + + + + + ``` -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! +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. diff --git a/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md b/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md index c1e013a8bec..ccf3e047075 100644 --- a/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md @@ -1,81 +1,224 @@ --- +title: Saving Photos to the Filesystem sidebar_label: Saving Photos --- + + Saving Photos to the Filesystem with Angular | Ionic Capacitor Camera + + + # Saving Photos to the Filesystem We’re now able to take multiple photos and display them in a photo gallery on the second tab of our app. These photos, however, are not currently being stored permanently, so when the app is closed, they will be deleted. ## Filesystem API -Fortunately, saving them to the filesystem only takes a few steps. Begin by creating a new class method, `savePicture()`, in the `PhotoService` class (`src/app/services/photo.service.ts`). We pass in the `photo` object, which represents the newly captured device photo: +Fortunately, saving them to the filesystem only takes a few steps. Begin by creating a new class method, `savePicture()`, in the `PhotoService` class. We pass in the `photo` object, which represents the newly captured device photo: + +```ts +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 { + // Same old code from before. + + // CHANGE: Add the `savePicture()` method. + private async savePicture(photo: Photo) { + return { + filepath: 'soon...', + webviewPath: 'soon...', + }; + } +} -```tsx -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) - }); - - // Save the picture and add it to photo collection - const savedImageFile = await this.savePicture(capturedPhoto); - this.photos.unshift(savedImageFile); +We can use this new method immediately in `addNewToGallery()`. + +```ts +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[] = []; + + // CHANGE: Update the `addNewToGallery()` method. + 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) { + return { + filepath: 'soon...', + webviewPath: 'soon...', + }; + } } -``` -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 - }); - - // 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, implement the logic for running on the web: +We'll use the Capacitor [Filesystem API](../../native/filesystem.md) to save the photo. First, convert the photo to base64 format. + +Then, pass the data to the Filesystem's `writeFile` method. Recall that we display photos by setting the image's source path (`src`) to the `webviewPath` property. So, set the `webviewPath` and return the new `Photo` object. + +For now, create a new helper method, `convertBlobToBase64()`, to implement the necessary logic for running on the web. + +```ts +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 { + // Same old code from before. + + // CHANGE: Update the `savePicture()` method. + private async savePicture(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(); + const base64Data = (await this.convertBlobToBase64(blob)) as string; + + // 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 `convertBlobToBase64` method. + private convertBlobToBase64(blob: Blob) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onerror = reject; + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(blob); + }); + } +} -```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(); +export interface UserPhoto { + filepath: string; + webviewPath?: string; +} +``` - return await this.convertBlobToBase64(blob) as string; +`photo.service.ts` should now look like this: + +```ts +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[] = []; + + 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) { + // Fetch the photo, read as a blob, then convert to base64 format + const response = await fetch(photo.webPath!); + const blob = await response.blob(); + const base64Data = (await this.convertBlobToBase64(blob)) as string; + + // 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 convertBlobToBase64(blob: Blob) { + return 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 { + 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. diff --git a/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md b/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md index 6f59d3f951f..2965d477e65 100644 --- a/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md @@ -1,69 +1,214 @@ --- +title: Loading Photos from the Filesystem sidebar_label: Loading Photos --- + + Loading Photos from the Filesystem with Angular | Ionic Capacitor Camera + + + # Loading Photos from the Filesystem We’ve implemented photo taking and saving to the filesystem. There’s one last piece of functionality missing: the photos are stored in the filesystem, but we need a way to save pointers to each file so that they can be displayed again in the photo gallery. -Fortunately, this is easy: we’ll leverage the Capacitor [Preferences API](https://capacitorjs.com/docs/apis/preferences) to store our array of Photos in a key-value store. +Fortunately, this is easy: we’ll leverage the Capacitor [Preferences API](../../native/preferences.md) to store our array of Photos in a key-value store. ## 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 +```ts export class PhotoService { public photos: UserPhoto[] = []; + + // CHANGE: Add a key for photo storage. private PHOTO_STORAGE: string = 'photos'; - // other code + // Same old code from before. } ``` -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. +Next, at the end of the `addNewToGallery()` method, 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. + +```ts +public async addNewToGallery() { + // Take a photo + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + quality: 100, + }); -```tsx -Preferences.set({ - key: this.PHOTO_STORAGE, - value: JSON.stringify(this.photos), -}); + const savedImageFile = await this.savePicture(capturedPhoto); + + this.photos.unshift(savedImageFile); + + // CHANGE: Add 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() { - // Retrieve cached photo array data - const { value } = await Preferences.get({ key: this.PHOTO_STORAGE }); - this.photos = (value ? JSON.parse(value) : []) as UserPhoto[]; +```ts +export class PhotoService { + // Same old code from before. + + // CHANGE: Add the method to load the photo data. + public async loadSaved() { + // Retrieve cached photo array data + const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; + } +} +``` - // more to come... +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()` method: + +```ts +export class PhotoService { + // Same old code from before. + + // CHANGE: Update the `loadSaved` method. + public async loadSaved() { + // Retrieve cached photo array data + const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; + + // 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 file = await Filesystem.file({ + path: photo.filepath, + directory: Directory.Data, + }); + + // Web platform only: Load the photo as base64 data + photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; + } + } } ``` -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: +`photo.service.ts` should now look like this: -```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, - }); +```ts +import { Injectable } from '@angular/core'; +import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Preferences } from '@capacitor/preferences'; - // Web platform only: Load the photo as base64 data - photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; +@Injectable({ + providedIn: 'root', +}) +export class PhotoService { + public photos: UserPhoto[] = []; + + private PHOTO_STORAGE: string = 'photos'; + + public async addNewToGallery() { + // Take a photo + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + quality: 100, + }); + + const savedImageFile = await this.savePicture(capturedPhoto); + + this.photos.unshift(savedImageFile); + + Preferences.set({ + key: this.PHOTO_STORAGE, + value: JSON.stringify(this.photos), + }); + } + + private async savePicture(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(); + const base64Data = (await this.convertBlobToBase64(blob)) as string; + + // 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 convertBlobToBase64(blob: Blob) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onerror = reject; + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(blob); + }); + } + + public async loadSaved() { + // Retrieve cached photo array data + const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; + } +} + +export interface UserPhoto { + filepath: string; + webviewPath?: string; } ``` -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. +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 the Photo Gallery, all photos are loaded and displayed on the screen. -```tsx -async ngOnInit() { - await this.photoService.loadSaved(); +Update `tab2.page.ts` to look like the following: + +```ts +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) {} + + // CHANGE: Add call to `loadSaved` when navigating to the 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). + +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/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md b/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md index ce7f60e75c5..25c10cb4cdf 100644 --- a/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md +++ b/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md @@ -1,7 +1,16 @@ --- +title: Adding Mobile strip_number_prefixes: false --- + + Adding Mobile Support with Angular | Ionic Capacitor Camera + + + # Adding Mobile Our photo gallery app won’t be complete until it runs on iOS, Android, and the web - all using one codebase. All it takes is some small logic changes to support mobile platforms, installing some native tooling, then running the app on a device. Let’s go! @@ -10,108 +19,274 @@ 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](../platform.md) 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 +```ts +import { Injectable } from '@angular/core'; +import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Preferences } from '@capacitor/preferences'; +// CHANGE: Add import. import { Platform } from '@ionic/angular'; export class PhotoService { public photos: UserPhoto[] = []; + private PHOTO_STORAGE: string = 'photos'; + + // CHANGE: Add a property to track the app's running platform. private platform: Platform; + // CHANGE: Update constructor to set `platform`. constructor(platform: Platform) { this.platform = platform; } - // other code + // Same old code from before. } ``` ## 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 `savePicture()` method, check which platform the app is running on. If it’s “hybrid” (Capacitor, the native runtime), then read the photo file into base64 format using the `Filesystem`'s' `readFile()` method. Otherwise, use the same logic as before when running the app on the web. + +Update `savePicture()` to look like the following: -```tsx -private async readAsBase64(photo: Photo) { +```ts +// CHANGE: Update the `savePicture()` method. +private async savePicture(photo: Photo) { + let base64Data: string | Blob; + + // CHANGE: Add platform check. // "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 { + base64Data = 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; + base64Data = await this.convertBlobToBase64(blob) as string; } + + // 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, + }; } ``` -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)). +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](../../core-concepts/webview.md#file-protocol)). To use this method, we'll need to import Capacitor into `photo.service.ts`. -```tsx -// Save picture to file on device +```ts +import { Capacitor } from '@capacitor/core'; +``` + +Then update `savePicture()` to look like the following: + +```ts +// CHANGE: Update `savePicture()` method. private async savePicture(photo: Photo) { - // Convert photo to base64 format, required by Filesystem API to save - const base64Data = await this.readAsBase64(photo); + let base64Data: string | Blob; + // "hybrid" will detect mobile - iOS or Android + if (this.platform.is('hybrid')) { + const file = await Filesystem.readFile({ + path: photo.path!, + }); + base64Data = 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(); + base64Data = await this.convertBlobToBase64(blob) as string; + } // Write the file to the data directory const fileName = Date.now() + '.jpeg'; const savedFile = await Filesystem.writeFile({ path: fileName, data: base64Data, - directory: Directory.Data + directory: Directory.Data, }); + // CHANGE: Add platform check. 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 { + } 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, }; } } ``` -Next, head back over to the `loadSaved()` function we implemented for the web earlier. On mobile, we can directly set the source of an image tag - `` - to each photo file on the Filesystem, displaying them automatically. Thus, only the web requires reading each image from the Filesystem into base64 format. Update this function to add an _if statement_ around the Filesystem code: +Next, add a new bit of logic in the `loadSaved()` method. On mobile, we can directly point to each photo file on the Filesystem and display them automatically. On the web, however, we must read each image from the Filesystem into base64 format. This is because the Filesystem API uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood. Update the `loadSaved()` method: -```tsx +```ts +// CHANGE: Update `loadSaved` method. 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[]; + const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; - // Easiest way to detect when running on the web: - // “when the platform is NOT hybrid, do this” + // CHANGE: Add platform check. + // If running on the web... 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({ + const file = await Filesystem.file({ path: photo.filepath, directory: Directory.Data }); // Web platform only: Load the photo as base64 data - photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; + photo.webviewPath = `data:image/jpeg;base64,${file.data}`; + } + } +} +``` + +Our Photo Gallery now consists of one codebase that runs on the web, Android, and iOS. + +`photos.service.ts` should now look like this: + +```ts +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, + }); + + const savedImageFile = await this.savePicture(capturedPhoto); + + this.photos.unshift(savedImageFile); + + Preferences.set({ + key: this.PHOTO_STORAGE, + value: JSON.stringify(this.photos), + }); + } + + private async savePicture(photo: Photo) { + let base64Data: string | Blob; + + // "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!, + }); + + base64Data = 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(); + + base64Data = (await this.convertBlobToBase64(blob)) as string; + } + + // 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 + 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 convertBlobToBase64(blob: Blob) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onerror = reject; + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(blob); + }); + } + + public async loadSaved() { + // Retrieve cached photo array data + const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; + + // If running on the web... + if (!this.platform.is('hybrid')) { + for (let photo of this.photos) { + const file = await Filesystem.file({ + path: photo.filepath, + directory: Directory.Data, + }); + // Web platform only: Load the photo as base64 data + photo.webviewPath = `data:image/jpeg;base64,${file.data}`; + } + } + } +} + +export interface UserPhoto { + filepath: string; + webviewPath?: string; } ``` -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. +Next up, the part you’ve been waiting for - deploying the app to a device. diff --git a/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md b/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md index e9637084b7b..3497cad7fba 100644 --- a/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md +++ b/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md @@ -4,20 +4,24 @@ sidebar_label: Deploying Mobile --- - Deploying to iOS and Android Apps - Capacitor Setup on Ionic + Adding Mobile Support with Angular | Ionic Capacitor Camera -Since we added Capacitor to our project when it was first created, there’s only a handful of steps remaining until the Photo Gallery app is on our device! Remember, you can find the complete source code for this app [here](https://github.com/ionic-team/photo-gallery-capacitor-ng). +Since we added Capacitor to our project when it was first created, there’s only a handful of steps remaining until the Photo Gallery app is on our device! + +:::note +Remember, you can find the complete source code for this app [here](https://github.com/ionic-team/photo-gallery-capacitor-ng). +::: ## Capacitor Setup Capacitor is Ionic’s official app runtime that makes it easy to deploy web apps to native platforms like iOS, Android, and more. If you’ve used Cordova in the past, consider reading more about the differences [here](https://capacitorjs.com/docs/cordova#differences-between-capacitor-and-cordova). -If you’re still running `ionic serve` in the terminal, cancel it. Complete a fresh build of your Ionic project, fixing any errors that it reports: +If you’re still running `ionic serve` in the terminal, cancel it. Complete a fresh build of the Ionic project, fixing any errors that it reports: ```shell ionic build @@ -26,8 +30,8 @@ ionic build Next, create both the iOS and Android projects: ```shell -$ ionic cap add ios -$ ionic cap add android +ionic cap add ios +ionic cap add android ``` Both android and ios folders at the root of the project are created. These are entirely standalone native projects that should be considered part of your Ionic app (i.e., check them into source control, edit them using their native tooling, etc.). @@ -44,7 +48,7 @@ Note: After making updates to the native portion of the code (such as adding a n ionic cap sync ``` -## iOS Deployment +## iOS :::note To build an iOS app, you’ll need a Mac computer. @@ -58,7 +62,7 @@ First, run the Capacitor `open` command, which opens the native iOS project in X ionic cap open ios ``` -In order for some native plugins to work, user permissions must be configured. In our photo gallery app, this includes the Camera plugin: iOS displays a modal dialog automatically after the first time that `Camera.getPhoto()` is called, prompting the user to allow the app to use the Camera. The permission that drives this is labeled “Privacy - Camera Usage.” To set it, the `Info.plist` file must be modified ([more details here](https://capacitorjs.com/docs/ios/configuration)). To access it, click "Info," then expand "Custom iOS Target Properties." +In order for some native plugins to work, user permissions must be configured. In our photo gallery app, this includes the Camera plugin: iOS displays a modal dialog automatically after the first time that `Camera.getPhoto()` is called, prompting the user to allow the app to use the Camera. The permission that drives this is labeled "Privacy - Camera Usage." To set it, the `Info.plist` file must be modified ([more details here](https://capacitorjs.com/docs/ios/configuration)). To access it, click "Info," then expand "Custom iOS Target Properties." ![The Info.plist file in Xcode showing the NSCameraUsageDescription key added for camera access.](/img/guides/first-app-cap-ng/xcode-info-plist.png 'Xcode Info.plist Configuration') @@ -80,7 +84,7 @@ Upon tapping the Camera button on the Photo Gallery tab, the permission prompt w ![Two iPhones side by side, one showing the camera permission prompt and the other displaying a photo taken with the app.](/img/guides/first-app-cap-ng/ios-permissions-photo.png 'iOS Camera Permission Prompt and Photo Result') -## Android Deployment +## Android Capacitor Android apps are configured and managed through Android Studio. Before running this app on an Android device, there's a couple of steps to complete. @@ -111,4 +115,4 @@ Once again, upon tapping the Camera button on the Photo Gallery tab, the permiss Our Photo Gallery app has just been deployed to Android and iOS devices. 🎉 -In the next portion of this tutorial, we’ll use the Ionic CLI’s Live Reload functionality to quickly implement photo deletion - thus completing our Photo Gallery feature. +In the final portion of this tutorial, we’ll use the Ionic CLI’s Live Reload functionality to quickly implement photo deletion - thus completing our Photo Gallery feature. diff --git a/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md b/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md index e6d0afe17f9..751f6cf0eaa 100644 --- a/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md +++ b/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md @@ -4,15 +4,18 @@ sidebar_label: Live Reload --- + Rapid App Development with Live Reload with Angular | Ionic Capacitor Camera +# Rapid App Development with Live Reload + So far, we’ve seen how easy it is to develop a cross-platform app that works everywhere. The development experience is pretty quick, but what if I told you there was a way to go faster? -We can use the Ionic CLI’s [Live Reload functionality](https://ionicframework.com/docs/cli/livereload) to boost our productivity when building Ionic apps. When active, Live Reload will reload the browser and/or WebView when changes in the app are detected. +We can use the Ionic CLI’s [Live Reload functionality](../../cli/livereload.md) to boost our productivity when building Ionic apps. When active, Live Reload will reload the browser and/or WebView when changes in the app are detected. ## Live Reload @@ -23,92 +26,143 @@ We can also use it when developing on iOS and Android devices. This is particula Let’s use Live Reload to implement photo deletion, the missing piece of our Photo Gallery feature. Select your platform of choice (iOS or Android) and connect a device to your computer. Next, run either command in a terminal, based on your chosen platform: ```shell -$ ionic cap run ios -l --external +ionic cap run ios -l --external -$ ionic cap run android -l --external +ionic cap run android -l --external ``` The Live Reload server will start up, and the native IDE of choice will open if not opened already. Within the IDE, click the Play button to launch the app onto your device. ## Deleting Photos -With Live Reload running and the app open on your device, let’s implement photo deletion functionality. In your code editor (not Android Studio or Xcode), 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. +With Live Reload running and the app open on your device, let’s implement photo deletion functionality. + +In `photo.service.ts`, add the `deletePhoto()` method. The selected photo is removed from the `photos` array first. Then, we use the Capacitor Preferences API to update the cached version of the `photos` array. Finally, we delete the actual photo file itself using the Filesystem API. + +```ts +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 { + // Same old code from before. + + // CHANGE: Add `deletePhoto()` method. + public async deletePhoto(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, + }); + } +} -```html - - - +export interface UserPhoto { + filepath: string; + webviewPath?: string; +} ``` -Over in `tab2.page.ts`, import Action Sheet and add it to the constructor: +Next, in `tab2.page.ts`, implement the `showActionSheet()` method. We're adding two options: "Delete", which calls `PhotoService.deletePhoto()`, and "Cancel". The cancel button will automatically closes the action sheet when assigned the "cancel" role. -```tsx +```ts +import { Component } from '@angular/core'; +// Change: Add import. +import type { UserPhoto } from '../services/photo.service'; +import { PhotoService } from '../services/photo.service'; +// CHANGE: Add import. import { ActionSheetController } from '@ionic/angular'; -constructor(public photoService: PhotoService, - public actionSheetController: ActionSheetController) {} -``` - -Add `UserPhoto` to the import statement. - -```tsx -import { PhotoService, UserPhoto } from '../services/photo.service'; -``` - -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 - } - }] - }); - await actionSheet.present(); +@Component({ + selector: 'app-tab2', + templateUrl: 'tab2.page.html', + styleUrls: ['tab2.page.scss'], + standalone: false, +}) +export class Tab2Page { + // CHANGE: Update constructor. + constructor(public photoService: PhotoService, public actionSheetController: ActionSheetController) {} + + // Same old code from before. + + // CHANGE: Add `showActionSheet` method. + 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.deletePhoto(photo, position); + }, + }, + { + text: 'Cancel', + icon: 'close', + role: 'cancel', + handler: () => { + // Nothing to do, action sheet is automatically closed + }, + }, + ], + }); + await actionSheet.present(); + } } ``` -Save both of the files we just edited. The Photo Gallery app will reload automatically, and now when we tap on one of the photos in the gallery, the action sheet displays. Tapping “Delete” doesn’t do anything yet, so head back into your code editor. - -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) - }); +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](../../api/action-sheet.md) dialog with the option to either delete the selected photo or cancel (close) the dialog. - // delete photo file from filesystem - const filename = photo.filepath - .substring(photo.filepath.lastIndexOf('/') + 1); - - await Filesystem.deleteFile({ - path: filename, - directory: Directory.Data - }); -} +```html + + + Photo Gallery + + + + + + + Photo Gallery + + + + + + + + + + + + + + + + + + ``` -The selected photo is removed from the Photos array first. Then, we use the Capacitor Preferences API to update the cached version of the Photos array. Finally, we delete the actual photo file itself using the Filesystem API. - -Save this file, then tap on a photo again and choose the “Delete” option. This time, the photo is deleted! Implemented much faster using Live Reload. 💪 +Tap on a photo again and choose the “Delete” option. The photo is deleted! Implemented much faster using Live Reload. 💪 In the final portion of this tutorial, we’ll walk you through the basics of the Appflow product used to build and deploy your application to users' devices. diff --git a/versioned_docs/version-v7/angular/your-first-app/8-distribute.md b/versioned_docs/version-v7/angular/your-first-app/8-distribute.md index 65ef475f6be..195ef764561 100644 --- a/versioned_docs/version-v7/angular/your-first-app/8-distribute.md +++ b/versioned_docs/version-v7/angular/your-first-app/8-distribute.md @@ -1,7 +1,16 @@ --- +title: Build and Distribute your App sidebar_label: Distribute --- + + Build and Deploy your App with Angular | Ionic Capacitor Camera + + + # Build and Deploy your App Now that you have built your first app, you are going to want to get it distributed so everyone can start using it. The mechanics of building and deploying your application can be quite cumbersome. That is where [Appflow](https://ionic.io/docs/appflow/) comes into play. Appflow allows you to effectively generate web and native builds, push out live app updates, publish your app to the app stores, and automate the whole process. The entire Quickstart guide can be found [here](https://ionic.io/docs/appflow/quickstart). @@ -52,7 +61,7 @@ Upon completion of the Web Build, additional versioning options are available to To receive this live update, you will need to run the app on a device or an emulator. The quickest and easiest way to do this is through the following command: ```shell -ionic [cordova | cap] run [ios | android] [options] +ionic cordova run [ios | android] [options] ``` Assuming the app is configured correctly to listen to the channel you deployed to, the app should immediately update on startup if you have chosen the auto update method during setup. If the background update method was chosen, be sure to stay in the app for about 30 seconds to ensure the update was downloaded. Then, close the application, reopen it, and you will see the updates applied! @@ -93,8 +102,8 @@ For access to the ability to create a Native Configuration, you will need to be ## What’s Next? -Congratulations! You developed a complete cross-platform Photo Gallery app that runs on the web, iOS, and Android. Not only that, you have also then built the app and deployed it to your users devices! +Congratulations! You developed a complete cross-platform Photo Gallery app that runs on the web, iOS, and Android. Not only that, you have also then built the app and deployed it to your users' devices! -There are many paths to follow from here. Try adding another [Ionic UI component](https://ionicframework.com/docs/components) to the app, or more [native functionality](https://capacitorjs.com/docs/apis). The sky’s the limit. Once you have added another feature, run the build and deploy process again through Appflow to get it out to your users. +There are many paths to follow from here. Try adding another [Ionic UI component](../../components.md) to the app, or more [native functionality](https://capacitorjs.com/docs/apis). The sky’s the limit. Once you have added another feature, run the build and deploy process again through Appflow to get it out to your users. Happy app building! 💙 From 899f1ea9e41729e187ee1abcdaf20fa85e4e2ec2 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 30 Oct 2025 16:57:29 -0700 Subject: [PATCH 53/68] docs(angular): update distribute pages --- docs/angular/your-first-app/8-distribute.md | 2 +- .../version-v7/angular/your-first-app/8-distribute.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/angular/your-first-app/8-distribute.md b/docs/angular/your-first-app/8-distribute.md index 195ef764561..4cfc993677c 100644 --- a/docs/angular/your-first-app/8-distribute.md +++ b/docs/angular/your-first-app/8-distribute.md @@ -61,7 +61,7 @@ Upon completion of the Web Build, additional versioning options are available to To receive this live update, you will need to run the app on a device or an emulator. The quickest and easiest way to do this is through the following command: ```shell -ionic cordova run [ios | android] [options] +ionic cap run [ios | android] [options] ``` Assuming the app is configured correctly to listen to the channel you deployed to, the app should immediately update on startup if you have chosen the auto update method during setup. If the background update method was chosen, be sure to stay in the app for about 30 seconds to ensure the update was downloaded. Then, close the application, reopen it, and you will see the updates applied! diff --git a/versioned_docs/version-v7/angular/your-first-app/8-distribute.md b/versioned_docs/version-v7/angular/your-first-app/8-distribute.md index 195ef764561..4cfc993677c 100644 --- a/versioned_docs/version-v7/angular/your-first-app/8-distribute.md +++ b/versioned_docs/version-v7/angular/your-first-app/8-distribute.md @@ -61,7 +61,7 @@ Upon completion of the Web Build, additional versioning options are available to To receive this live update, you will need to run the app on a device or an emulator. The quickest and easiest way to do this is through the following command: ```shell -ionic cordova run [ios | android] [options] +ionic cap run [ios | android] [options] ``` Assuming the app is configured correctly to listen to the channel you deployed to, the app should immediately update on startup if you have chosen the auto update method during setup. If the background update method was chosen, be sure to stay in the app for about 30 seconds to ensure the update was downloaded. Then, close the application, reopen it, and you will see the updates applied! From 4d230547b5b1858031e31c68ee3f947da6c2e002 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 30 Oct 2025 17:00:32 -0700 Subject: [PATCH 54/68] docs(angular): remove first header --- docs/angular/your-first-app.md | 2 -- docs/angular/your-first-app/2-taking-photos.md | 2 -- docs/angular/your-first-app/3-saving-photos.md | 2 -- docs/angular/your-first-app/4-loading-photos.md | 2 -- docs/angular/your-first-app/5-adding-mobile.md | 2 -- docs/angular/your-first-app/7-live-reload.md | 2 -- docs/angular/your-first-app/8-distribute.md | 2 -- versioned_docs/version-v7/angular/your-first-app.md | 2 -- .../version-v7/angular/your-first-app/2-taking-photos.md | 2 -- .../version-v7/angular/your-first-app/3-saving-photos.md | 2 -- .../version-v7/angular/your-first-app/4-loading-photos.md | 2 -- .../version-v7/angular/your-first-app/5-adding-mobile.md | 2 -- .../version-v7/angular/your-first-app/7-live-reload.md | 2 -- .../version-v7/angular/your-first-app/8-distribute.md | 2 -- 14 files changed, 28 deletions(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 028b92151be..1231f517757 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -11,8 +11,6 @@ sidebar_label: Build Your First App /> -# Your First Ionic App: Angular - The great thing about Ionic is that with one codebase, you can build for any platform using just HTML, CSS, and JavaScript. Follow along as we learn the fundamentals of Ionic app development by creating a realistic app step by step. Here’s the finished app running on all 3 platforms: diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index d888ac6d94b..3709eb883b8 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -11,8 +11,6 @@ sidebar_label: Taking Photos /> -# Taking Photos with the Camera - Now for the fun part - adding the ability to take photos with the device’s camera using the Capacitor [Camera API](../../native/camera.md). We’ll begin with building it for the web, then make some small tweaks to make it work on mobile (iOS and Android). ## Photo Service diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index ccf3e047075..1d5f27b355d 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -11,8 +11,6 @@ sidebar_label: Saving Photos /> -# Saving Photos to the Filesystem - We’re now able to take multiple photos and display them in a photo gallery on the second tab of our app. These photos, however, are not currently being stored permanently, so when the app is closed, they will be deleted. ## Filesystem API diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index 2965d477e65..b766bd6319a 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -11,8 +11,6 @@ sidebar_label: Loading Photos /> -# Loading Photos from the Filesystem - We’ve implemented photo taking and saving to the filesystem. There’s one last piece of functionality missing: the photos are stored in the filesystem, but we need a way to save pointers to each file so that they can be displayed again in the photo gallery. Fortunately, this is easy: we’ll leverage the Capacitor [Preferences API](../../native/preferences.md) to store our array of Photos in a key-value store. diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 25c10cb4cdf..bf27c9c6ece 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -11,8 +11,6 @@ strip_number_prefixes: false /> -# Adding Mobile - Our photo gallery app won’t be complete until it runs on iOS, Android, and the web - all using one codebase. All it takes is some small logic changes to support mobile platforms, installing some native tooling, then running the app on a device. Let’s go! ## Import Platform API diff --git a/docs/angular/your-first-app/7-live-reload.md b/docs/angular/your-first-app/7-live-reload.md index 751f6cf0eaa..fec830f6be3 100644 --- a/docs/angular/your-first-app/7-live-reload.md +++ b/docs/angular/your-first-app/7-live-reload.md @@ -11,8 +11,6 @@ sidebar_label: Live Reload /> -# Rapid App Development with Live Reload - So far, we’ve seen how easy it is to develop a cross-platform app that works everywhere. The development experience is pretty quick, but what if I told you there was a way to go faster? We can use the Ionic CLI’s [Live Reload functionality](../../cli/livereload.md) to boost our productivity when building Ionic apps. When active, Live Reload will reload the browser and/or WebView when changes in the app are detected. diff --git a/docs/angular/your-first-app/8-distribute.md b/docs/angular/your-first-app/8-distribute.md index 4cfc993677c..bf495f68421 100644 --- a/docs/angular/your-first-app/8-distribute.md +++ b/docs/angular/your-first-app/8-distribute.md @@ -11,8 +11,6 @@ sidebar_label: Distribute /> -# Build and Deploy your App - Now that you have built your first app, you are going to want to get it distributed so everyone can start using it. The mechanics of building and deploying your application can be quite cumbersome. That is where [Appflow](https://ionic.io/docs/appflow/) comes into play. Appflow allows you to effectively generate web and native builds, push out live app updates, publish your app to the app stores, and automate the whole process. The entire Quickstart guide can be found [here](https://ionic.io/docs/appflow/quickstart). Below we will run through an overview of the steps. diff --git a/versioned_docs/version-v7/angular/your-first-app.md b/versioned_docs/version-v7/angular/your-first-app.md index 028b92151be..1231f517757 100644 --- a/versioned_docs/version-v7/angular/your-first-app.md +++ b/versioned_docs/version-v7/angular/your-first-app.md @@ -11,8 +11,6 @@ sidebar_label: Build Your First App /> -# Your First Ionic App: Angular - The great thing about Ionic is that with one codebase, you can build for any platform using just HTML, CSS, and JavaScript. Follow along as we learn the fundamentals of Ionic app development by creating a realistic app step by step. Here’s the finished app running on all 3 platforms: diff --git a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md index d888ac6d94b..3709eb883b8 100644 --- a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md @@ -11,8 +11,6 @@ sidebar_label: Taking Photos /> -# Taking Photos with the Camera - Now for the fun part - adding the ability to take photos with the device’s camera using the Capacitor [Camera API](../../native/camera.md). We’ll begin with building it for the web, then make some small tweaks to make it work on mobile (iOS and Android). ## Photo Service diff --git a/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md b/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md index ccf3e047075..1d5f27b355d 100644 --- a/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md @@ -11,8 +11,6 @@ sidebar_label: Saving Photos /> -# Saving Photos to the Filesystem - We’re now able to take multiple photos and display them in a photo gallery on the second tab of our app. These photos, however, are not currently being stored permanently, so when the app is closed, they will be deleted. ## Filesystem API diff --git a/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md b/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md index 2965d477e65..b766bd6319a 100644 --- a/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md @@ -11,8 +11,6 @@ sidebar_label: Loading Photos /> -# Loading Photos from the Filesystem - We’ve implemented photo taking and saving to the filesystem. There’s one last piece of functionality missing: the photos are stored in the filesystem, but we need a way to save pointers to each file so that they can be displayed again in the photo gallery. Fortunately, this is easy: we’ll leverage the Capacitor [Preferences API](../../native/preferences.md) to store our array of Photos in a key-value store. diff --git a/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md b/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md index 25c10cb4cdf..bf27c9c6ece 100644 --- a/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md +++ b/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md @@ -11,8 +11,6 @@ strip_number_prefixes: false /> -# Adding Mobile - Our photo gallery app won’t be complete until it runs on iOS, Android, and the web - all using one codebase. All it takes is some small logic changes to support mobile platforms, installing some native tooling, then running the app on a device. Let’s go! ## Import Platform API diff --git a/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md b/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md index 751f6cf0eaa..fec830f6be3 100644 --- a/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md +++ b/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md @@ -11,8 +11,6 @@ sidebar_label: Live Reload /> -# Rapid App Development with Live Reload - So far, we’ve seen how easy it is to develop a cross-platform app that works everywhere. The development experience is pretty quick, but what if I told you there was a way to go faster? We can use the Ionic CLI’s [Live Reload functionality](../../cli/livereload.md) to boost our productivity when building Ionic apps. When active, Live Reload will reload the browser and/or WebView when changes in the app are detected. diff --git a/versioned_docs/version-v7/angular/your-first-app/8-distribute.md b/versioned_docs/version-v7/angular/your-first-app/8-distribute.md index 4cfc993677c..bf495f68421 100644 --- a/versioned_docs/version-v7/angular/your-first-app/8-distribute.md +++ b/versioned_docs/version-v7/angular/your-first-app/8-distribute.md @@ -11,8 +11,6 @@ sidebar_label: Distribute /> -# Build and Deploy your App - Now that you have built your first app, you are going to want to get it distributed so everyone can start using it. The mechanics of building and deploying your application can be quite cumbersome. That is where [Appflow](https://ionic.io/docs/appflow/) comes into play. Appflow allows you to effectively generate web and native builds, push out live app updates, publish your app to the app stores, and automate the whole process. The entire Quickstart guide can be found [here](https://ionic.io/docs/appflow/quickstart). Below we will run through an overview of the steps. From c0d4ea830eee982a4e158b5ef855116255c2f902 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 30 Oct 2025 17:02:30 -0700 Subject: [PATCH 55/68] docs(angular): update your first app pages --- docs/angular/your-first-app.md | 2 +- versioned_docs/version-v7/angular/your-first-app.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 1231f517757..28a6343cd80 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -134,7 +134,7 @@ ionic serve And voilà! Your Ionic app is now running in a web browser. Most of your app can be built and tested right in the browser, greatly increasing development and testing speed. -## Photo Gallery!!! +## Photo Gallery There are three tabs. Click on the "Tab2" tab. It’s a blank canvas, aka the perfect spot to transform into a Photo Gallery. The Ionic CLI features Live Reload, so when you make changes and save them, the app is updated immediately! diff --git a/versioned_docs/version-v7/angular/your-first-app.md b/versioned_docs/version-v7/angular/your-first-app.md index 1231f517757..28a6343cd80 100644 --- a/versioned_docs/version-v7/angular/your-first-app.md +++ b/versioned_docs/version-v7/angular/your-first-app.md @@ -134,7 +134,7 @@ ionic serve And voilà! Your Ionic app is now running in a web browser. Most of your app can be built and tested right in the browser, greatly increasing development and testing speed. -## Photo Gallery!!! +## Photo Gallery There are three tabs. Click on the "Tab2" tab. It’s a blank canvas, aka the perfect spot to transform into a Photo Gallery. The Ionic CLI features Live Reload, so when you make changes and save them, the app is updated immediately! From cc3843f647a50ecab35e51612a8079e068680285 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 30 Oct 2025 17:04:27 -0700 Subject: [PATCH 56/68] docs(angular): update taking photos pages --- docs/angular/your-first-app/2-taking-photos.md | 2 +- .../version-v7/angular/your-first-app/2-taking-photos.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 3709eb883b8..60254b9af82 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -57,7 +57,7 @@ export class PhotoService { } ``` -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 - `getPhoto()` - that will open up the device's camera and allow us to take photos. +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. Next, in `tab2.page.ts`, import the `PhotoService` class and add a method to call its `addNewToGallery` method. diff --git a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md index 3709eb883b8..60254b9af82 100644 --- a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md @@ -57,7 +57,7 @@ export class PhotoService { } ``` -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 - `getPhoto()` - that will open up the device's camera and allow us to take photos. +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. Next, in `tab2.page.ts`, import the `PhotoService` class and add a method to call its `addNewToGallery` method. From 1e4305ecfa322adea224bf834c34be876a498930 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 30 Oct 2025 17:07:34 -0700 Subject: [PATCH 57/68] docs(angular): change wording --- docs/angular/your-first-app/2-taking-photos.md | 4 +--- .../version-v7/angular/your-first-app/2-taking-photos.md | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 60254b9af82..511e2f1de98 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -118,9 +118,7 @@ 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: +To define the data structure for our photo metadata, create a new interface named `UserPhoto`. Add this interface at the very bottom of the `photo.service.ts` file, immediately after the `PhotoService` class definition: ```ts export class PhotoService { diff --git a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md index 60254b9af82..511e2f1de98 100644 --- a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md @@ -118,9 +118,7 @@ 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: +To define the data structure for our photo metadata, create a new interface named `UserPhoto`. Add this interface at the very bottom of the `photo.service.ts` file, immediately after the `PhotoService` class definition: ```ts export class PhotoService { From 32c1a4b2e96e4b0eceef0d15eb20505ad0b2a387 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 30 Oct 2025 17:13:03 -0700 Subject: [PATCH 58/68] docs(angular): change existing code text --- docs/angular/your-first-app/2-taking-photos.md | 4 ++-- docs/angular/your-first-app/3-saving-photos.md | 4 ++-- docs/angular/your-first-app/4-loading-photos.md | 6 +++--- docs/angular/your-first-app/5-adding-mobile.md | 2 +- docs/angular/your-first-app/7-live-reload.md | 4 ++-- .../version-v7/angular/your-first-app/2-taking-photos.md | 4 ++-- .../version-v7/angular/your-first-app/3-saving-photos.md | 4 ++-- .../version-v7/angular/your-first-app/4-loading-photos.md | 6 +++--- .../version-v7/angular/your-first-app/5-adding-mobile.md | 2 +- .../version-v7/angular/your-first-app/7-live-reload.md | 4 ++-- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 511e2f1de98..854a68b8d70 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -122,7 +122,7 @@ To define the data structure for our photo metadata, create a new interface name ```ts export class PhotoService { - // Same old code from before. + // ...existing code... } // CHANGE: Add the `UserPhoto` interface. @@ -140,7 +140,7 @@ export class PhotoService { public photos: UserPhoto[] = []; public async addNewToGallery() { - // Same old code from before. + // ...existing code... } } ``` diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index 1d5f27b355d..93643dbcbb9 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -27,7 +27,7 @@ import { Preferences } from '@capacitor/preferences'; providedIn: 'root', }) export class PhotoService { - // Same old code from before. + // ...existing code... // CHANGE: Add the `savePicture()` method. private async savePicture(photo: Photo) { @@ -105,7 +105,7 @@ import { Preferences } from '@capacitor/preferences'; providedIn: 'root', }) export class PhotoService { - // Same old code from before. + // ...existing code... // CHANGE: Update the `savePicture()` method. private async savePicture(photo: Photo) { diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index b766bd6319a..11d1546df8a 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -26,7 +26,7 @@ export class PhotoService { // CHANGE: Add a key for photo storage. private PHOTO_STORAGE: string = 'photos'; - // Same old code from before. + // ...existing code... } ``` @@ -57,7 +57,7 @@ With the photo array data saved, create a new public method in the `PhotoService ```ts export class PhotoService { - // Same old code from before. + // ...existing code... // CHANGE: Add the method to load the photo data. public async loadSaved() { @@ -72,7 +72,7 @@ On mobile (coming up next!), we can directly set the source of an image tag - `< ```ts export class PhotoService { - // Same old code from before. + // ...existing code... // CHANGE: Update the `loadSaved` method. public async loadSaved() { diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index bf27c9c6ece..c8ee387b212 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -42,7 +42,7 @@ export class PhotoService { this.platform = platform; } - // Same old code from before. + // ...existing code... } ``` diff --git a/docs/angular/your-first-app/7-live-reload.md b/docs/angular/your-first-app/7-live-reload.md index fec830f6be3..500990df1c7 100644 --- a/docs/angular/your-first-app/7-live-reload.md +++ b/docs/angular/your-first-app/7-live-reload.md @@ -49,7 +49,7 @@ import { Capacitor } from '@capacitor/core'; providedIn: 'root', }) export class PhotoService { - // Same old code from before. + // ...existing code... // CHANGE: Add `deletePhoto()` method. public async deletePhoto(photo: UserPhoto, position: number) { @@ -98,7 +98,7 @@ export class Tab2Page { // CHANGE: Update constructor. constructor(public photoService: PhotoService, public actionSheetController: ActionSheetController) {} - // Same old code from before. + // ...existing code... // CHANGE: Add `showActionSheet` method. public async showActionSheet(photo: UserPhoto, position: number) { diff --git a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md index 511e2f1de98..854a68b8d70 100644 --- a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md @@ -122,7 +122,7 @@ To define the data structure for our photo metadata, create a new interface name ```ts export class PhotoService { - // Same old code from before. + // ...existing code... } // CHANGE: Add the `UserPhoto` interface. @@ -140,7 +140,7 @@ export class PhotoService { public photos: UserPhoto[] = []; public async addNewToGallery() { - // Same old code from before. + // ...existing code... } } ``` diff --git a/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md b/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md index 1d5f27b355d..93643dbcbb9 100644 --- a/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md @@ -27,7 +27,7 @@ import { Preferences } from '@capacitor/preferences'; providedIn: 'root', }) export class PhotoService { - // Same old code from before. + // ...existing code... // CHANGE: Add the `savePicture()` method. private async savePicture(photo: Photo) { @@ -105,7 +105,7 @@ import { Preferences } from '@capacitor/preferences'; providedIn: 'root', }) export class PhotoService { - // Same old code from before. + // ...existing code... // CHANGE: Update the `savePicture()` method. private async savePicture(photo: Photo) { diff --git a/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md b/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md index b766bd6319a..11d1546df8a 100644 --- a/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md @@ -26,7 +26,7 @@ export class PhotoService { // CHANGE: Add a key for photo storage. private PHOTO_STORAGE: string = 'photos'; - // Same old code from before. + // ...existing code... } ``` @@ -57,7 +57,7 @@ With the photo array data saved, create a new public method in the `PhotoService ```ts export class PhotoService { - // Same old code from before. + // ...existing code... // CHANGE: Add the method to load the photo data. public async loadSaved() { @@ -72,7 +72,7 @@ On mobile (coming up next!), we can directly set the source of an image tag - `< ```ts export class PhotoService { - // Same old code from before. + // ...existing code... // CHANGE: Update the `loadSaved` method. public async loadSaved() { diff --git a/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md b/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md index bf27c9c6ece..c8ee387b212 100644 --- a/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md +++ b/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md @@ -42,7 +42,7 @@ export class PhotoService { this.platform = platform; } - // Same old code from before. + // ...existing code... } ``` diff --git a/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md b/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md index fec830f6be3..500990df1c7 100644 --- a/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md +++ b/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md @@ -49,7 +49,7 @@ import { Capacitor } from '@capacitor/core'; providedIn: 'root', }) export class PhotoService { - // Same old code from before. + // ...existing code... // CHANGE: Add `deletePhoto()` method. public async deletePhoto(photo: UserPhoto, position: number) { @@ -98,7 +98,7 @@ export class Tab2Page { // CHANGE: Update constructor. constructor(public photoService: PhotoService, public actionSheetController: ActionSheetController) {} - // Same old code from before. + // ...existing code... // CHANGE: Add `showActionSheet` method. public async showActionSheet(photo: UserPhoto, position: number) { From f178e081d628ce78fa3f0c560f981ed3c0a30e29 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 30 Oct 2025 17:19:49 -0700 Subject: [PATCH 59/68] docs(angular): remove periods and add () --- docs/angular/your-first-app.md | 4 ++-- docs/angular/your-first-app/2-taking-photos.md | 18 +++++++++--------- docs/angular/your-first-app/3-saving-photos.md | 10 +++++----- .../angular/your-first-app/4-loading-photos.md | 14 +++++++------- docs/angular/your-first-app/5-adding-mobile.md | 18 +++++++++--------- docs/angular/your-first-app/7-live-reload.md | 10 +++++----- .../version-v7/angular/your-first-app.md | 4 ++-- .../angular/your-first-app/2-taking-photos.md | 18 +++++++++--------- .../angular/your-first-app/3-saving-photos.md | 10 +++++----- .../angular/your-first-app/4-loading-photos.md | 14 +++++++------- .../angular/your-first-app/5-adding-mobile.md | 18 +++++++++--------- .../angular/your-first-app/7-live-reload.md | 10 +++++----- 12 files changed, 74 insertions(+), 74 deletions(-) diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 28a6343cd80..593a12582a3 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -111,10 +111,10 @@ Next, import `@ionic/pwa-elements` by editing `src/main.ts`. ```ts import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; -// CHANGE: Add the following import. +// CHANGE: Add the following import import { defineCustomElements } from '@ionic/pwa-elements/loader'; -// CHANGE: Call the element loader before the `bootstrapModule` call. +// CHANGE: Call the element loader before the `bootstrapModule` call defineCustomElements(window); platformBrowserDynamic() diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 854a68b8d70..0acdc5b824b 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -25,7 +25,7 @@ Open the new `services/photo.service.ts` file, and let’s add the logic that wi ```ts import { Injectable } from '@angular/core'; -// CHANGE: Add the following imports. +// CHANGE: Add the following imports import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; @@ -45,7 +45,7 @@ import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; export class PhotoService { - // CHANGE: Add the gallery function. + // CHANGE: Add the gallery method public async addNewToGallery() { // Take a photo const capturedPhoto = await Camera.getPhoto({ @@ -63,7 +63,7 @@ Next, in `tab2.page.ts`, import the `PhotoService` class and add a method to cal ```ts import { Component } from '@angular/core'; -// CHANGE: Import the PhotoService. +// CHANGE: Import the PhotoService import { PhotoService } from '../services/photo.service'; @Component({ @@ -73,10 +73,10 @@ import { PhotoService } from '../services/photo.service'; standalone: false, }) export class Tab2Page { - // CHANGE: Update constructor to include `photoService`. + // CHANGE: Update constructor to include `photoService` constructor(public photoService: PhotoService) {} - // CHANGE: Add `addNewToGallery` method. + // CHANGE: Add `addNewToGallery()` method addPhotoToGallery() { this.photoService.addNewToGallery(); } @@ -125,7 +125,7 @@ export class PhotoService { // ...existing code... } -// CHANGE: Add the `UserPhoto` interface. +// CHANGE: Add the `UserPhoto` interface export interface UserPhoto { filepath: string; webviewPath?: string; @@ -136,7 +136,7 @@ Above the `addNewToGallery()` method, define an array of `UserPhoto`, which will ```ts export class PhotoService { - // CHANGE: Add the `photos` array. + // CHANGE: Add the `photos` array public photos: UserPhoto[] = []; public async addNewToGallery() { @@ -148,7 +148,7 @@ export class PhotoService { Over in the `addNewToGallery` method, add the newly captured photo to the beginning of the `photos` array. ```ts -// CHANGE: Update `addNewToGallery()` method. +// CHANGE: Update `addNewToGallery()` method public async addNewToGallery() { // Take a photo const capturedPhoto = await Camera.getPhoto({ @@ -157,7 +157,7 @@ public async addNewToGallery() { quality: 100 }); - // CHANGE: Add the new photo to the photos array. + // CHANGE: Add the new photo to the photos array this.photos.unshift({ filepath: "soon...", webviewPath: capturedPhoto.webPath! diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index 93643dbcbb9..5c9d3ccd9e3 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -29,7 +29,7 @@ import { Preferences } from '@capacitor/preferences'; export class PhotoService { // ...existing code... - // CHANGE: Add the `savePicture()` method. + // CHANGE: Add the `savePicture()` method private async savePicture(photo: Photo) { return { filepath: 'soon...', @@ -58,7 +58,7 @@ import { Preferences } from '@capacitor/preferences'; export class PhotoService { public photos: UserPhoto[] = []; - // CHANGE: Update the `addNewToGallery()` method. + // CHANGE: Update the `addNewToGallery()` method public async addNewToGallery() { // Take a photo const capturedPhoto = await Camera.getPhoto({ @@ -67,7 +67,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); @@ -107,7 +107,7 @@ import { Preferences } from '@capacitor/preferences'; export class PhotoService { // ...existing code... - // CHANGE: Update the `savePicture()` method. + // CHANGE: Update the `savePicture()` method private async savePicture(photo: Photo) { // Fetch the photo, read as a blob, then convert to base64 format const response = await fetch(photo.webPath!); @@ -130,7 +130,7 @@ export class PhotoService { }; } - // CHANGE: Add the `convertBlobToBase64` method. + // CHANGE: Add the `convertBlobToBase64` method private convertBlobToBase64(blob: Blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index 11d1546df8a..46b151aece7 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -23,7 +23,7 @@ Open `photo.service.ts` and begin by defining a new property in the `PhotoServic export class PhotoService { public photos: UserPhoto[] = []; - // CHANGE: Add a key for photo storage. + // CHANGE: Add a key for photo storage private PHOTO_STORAGE: string = 'photos'; // ...existing code... @@ -45,7 +45,7 @@ public async addNewToGallery() { this.photos.unshift(savedImageFile); - // CHANGE: Add 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), @@ -59,7 +59,7 @@ With the photo array data saved, create a new public method in the `PhotoService export class PhotoService { // ...existing code... - // CHANGE: Add the method to load the photo data. + // CHANGE: Add the method to load the photo data public async loadSaved() { // Retrieve cached photo array data const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); @@ -74,13 +74,13 @@ On mobile (coming up next!), we can directly set the source of an image tag - `< export class PhotoService { // ...existing code... - // CHANGE: Update the `loadSaved` method. + // CHANGE: Update the `loadSaved()` method public async loadSaved() { // Retrieve cached photo array data const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; - // CHANGE: Display the photo by reading into base64 format. + // 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 file = await Filesystem.file({ @@ -175,7 +175,7 @@ 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 the Photo Gallery, all photos are loaded and displayed on the screen. +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 the Photo Gallery, all photos are loaded and displayed on the screen. Update `tab2.page.ts` to look like the following: @@ -192,7 +192,7 @@ import { PhotoService } from '../services/photo.service'; export class Tab2Page { constructor(public photoService: PhotoService) {} - // CHANGE: Add call to `loadSaved` when navigating to the 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 c8ee387b212..56f57952cb5 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 @@ import { Injectable } from '@angular/core'; import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; -// CHANGE: Add import. +// CHANGE: Add import import { Platform } from '@ionic/angular'; export class PhotoService { @@ -34,10 +34,10 @@ export class PhotoService { private PHOTO_STORAGE: string = 'photos'; - // CHANGE: Add a property to track the app's running platform. + // CHANGE: Add a property to track the app's running platform private platform: Platform; - // CHANGE: Update constructor to set `platform`. + // CHANGE: Update constructor to set `platform` constructor(platform: Platform) { this.platform = platform; } @@ -53,11 +53,11 @@ First, we’ll update the photo saving functionality to support mobile. In the ` Update `savePicture()` to look like the following: ```ts -// CHANGE: Update the `savePicture()` method. +// CHANGE: Update the `savePicture()` method private async savePicture(photo: Photo) { let base64Data: string | Blob; - // CHANGE: Add platform check. + // CHANGE: Add platform check // "hybrid" will detect Cordova or Capacitor if (this.platform.is('hybrid')) { // Read the file into base64 format @@ -98,7 +98,7 @@ import { Capacitor } from '@capacitor/core'; Then update `savePicture()` to look like the following: ```ts -// CHANGE: Update `savePicture()` method. +// CHANGE: Update `savePicture()` method private async savePicture(photo: Photo) { let base64Data: string | Blob; // "hybrid" will detect mobile - iOS or Android @@ -122,7 +122,7 @@ private async savePicture(photo: Photo) { directory: Directory.Data, }); - // CHANGE: Add platform check. + // CHANGE: Add platform check if (this.platform.is('hybrid')) { // Display the new image by rewriting the 'file://' path to HTTP return { @@ -143,12 +143,12 @@ private async savePicture(photo: Photo) { Next, add a new bit of logic in the `loadSaved()` method. On mobile, we can directly point to each photo file on the Filesystem and display them automatically. On the web, however, we must read each image from the Filesystem into base64 format. This is because the Filesystem API uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood. Update the `loadSaved()` method: ```ts -// CHANGE: Update `loadSaved` method. +// CHANGE: Update `loadSaved()` method public async loadSaved() { const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; - // CHANGE: Add platform check. + // CHANGE: Add platform check // If running on the web... if (!this.platform.is('hybrid')) { for (let photo of this.photos) { diff --git a/docs/angular/your-first-app/7-live-reload.md b/docs/angular/your-first-app/7-live-reload.md index 500990df1c7..6c3b631981f 100644 --- a/docs/angular/your-first-app/7-live-reload.md +++ b/docs/angular/your-first-app/7-live-reload.md @@ -51,7 +51,7 @@ import { Capacitor } from '@capacitor/core'; export class PhotoService { // ...existing code... - // CHANGE: Add `deletePhoto()` method. + // CHANGE: Add `deletePhoto()` method public async deletePhoto(photo: UserPhoto, position: number) { // Remove this photo from the Photos reference data array this.photos.splice(position, 1); @@ -82,10 +82,10 @@ Next, in `tab2.page.ts`, implement the `showActionSheet()` method. We're adding ```ts import { Component } from '@angular/core'; -// Change: Add import. +// Change: Add import import type { UserPhoto } from '../services/photo.service'; import { PhotoService } from '../services/photo.service'; -// CHANGE: Add import. +// CHANGE: Add import import { ActionSheetController } from '@ionic/angular'; @Component({ @@ -95,12 +95,12 @@ import { ActionSheetController } from '@ionic/angular'; standalone: false, }) export class Tab2Page { - // CHANGE: Update constructor. + // CHANGE: Update constructor constructor(public photoService: PhotoService, public actionSheetController: ActionSheetController) {} // ...existing code... - // CHANGE: Add `showActionSheet` method. + // CHANGE: Add `showActionSheet()` method public async showActionSheet(photo: UserPhoto, position: number) { const actionSheet = await this.actionSheetController.create({ header: 'Photos', diff --git a/versioned_docs/version-v7/angular/your-first-app.md b/versioned_docs/version-v7/angular/your-first-app.md index 28a6343cd80..593a12582a3 100644 --- a/versioned_docs/version-v7/angular/your-first-app.md +++ b/versioned_docs/version-v7/angular/your-first-app.md @@ -111,10 +111,10 @@ Next, import `@ionic/pwa-elements` by editing `src/main.ts`. ```ts import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; -// CHANGE: Add the following import. +// CHANGE: Add the following import import { defineCustomElements } from '@ionic/pwa-elements/loader'; -// CHANGE: Call the element loader before the `bootstrapModule` call. +// CHANGE: Call the element loader before the `bootstrapModule` call defineCustomElements(window); platformBrowserDynamic() diff --git a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md index 854a68b8d70..0acdc5b824b 100644 --- a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md @@ -25,7 +25,7 @@ Open the new `services/photo.service.ts` file, and let’s add the logic that wi ```ts import { Injectable } from '@angular/core'; -// CHANGE: Add the following imports. +// CHANGE: Add the following imports import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; @@ -45,7 +45,7 @@ import { Filesystem, Directory } from '@capacitor/filesystem'; import { Preferences } from '@capacitor/preferences'; export class PhotoService { - // CHANGE: Add the gallery function. + // CHANGE: Add the gallery method public async addNewToGallery() { // Take a photo const capturedPhoto = await Camera.getPhoto({ @@ -63,7 +63,7 @@ Next, in `tab2.page.ts`, import the `PhotoService` class and add a method to cal ```ts import { Component } from '@angular/core'; -// CHANGE: Import the PhotoService. +// CHANGE: Import the PhotoService import { PhotoService } from '../services/photo.service'; @Component({ @@ -73,10 +73,10 @@ import { PhotoService } from '../services/photo.service'; standalone: false, }) export class Tab2Page { - // CHANGE: Update constructor to include `photoService`. + // CHANGE: Update constructor to include `photoService` constructor(public photoService: PhotoService) {} - // CHANGE: Add `addNewToGallery` method. + // CHANGE: Add `addNewToGallery()` method addPhotoToGallery() { this.photoService.addNewToGallery(); } @@ -125,7 +125,7 @@ export class PhotoService { // ...existing code... } -// CHANGE: Add the `UserPhoto` interface. +// CHANGE: Add the `UserPhoto` interface export interface UserPhoto { filepath: string; webviewPath?: string; @@ -136,7 +136,7 @@ Above the `addNewToGallery()` method, define an array of `UserPhoto`, which will ```ts export class PhotoService { - // CHANGE: Add the `photos` array. + // CHANGE: Add the `photos` array public photos: UserPhoto[] = []; public async addNewToGallery() { @@ -148,7 +148,7 @@ export class PhotoService { Over in the `addNewToGallery` method, add the newly captured photo to the beginning of the `photos` array. ```ts -// CHANGE: Update `addNewToGallery()` method. +// CHANGE: Update `addNewToGallery()` method public async addNewToGallery() { // Take a photo const capturedPhoto = await Camera.getPhoto({ @@ -157,7 +157,7 @@ public async addNewToGallery() { quality: 100 }); - // CHANGE: Add the new photo to the photos array. + // CHANGE: Add the new photo to the photos array this.photos.unshift({ filepath: "soon...", webviewPath: capturedPhoto.webPath! diff --git a/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md b/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md index 93643dbcbb9..5c9d3ccd9e3 100644 --- a/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/3-saving-photos.md @@ -29,7 +29,7 @@ import { Preferences } from '@capacitor/preferences'; export class PhotoService { // ...existing code... - // CHANGE: Add the `savePicture()` method. + // CHANGE: Add the `savePicture()` method private async savePicture(photo: Photo) { return { filepath: 'soon...', @@ -58,7 +58,7 @@ import { Preferences } from '@capacitor/preferences'; export class PhotoService { public photos: UserPhoto[] = []; - // CHANGE: Update the `addNewToGallery()` method. + // CHANGE: Update the `addNewToGallery()` method public async addNewToGallery() { // Take a photo const capturedPhoto = await Camera.getPhoto({ @@ -67,7 +67,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); @@ -107,7 +107,7 @@ import { Preferences } from '@capacitor/preferences'; export class PhotoService { // ...existing code... - // CHANGE: Update the `savePicture()` method. + // CHANGE: Update the `savePicture()` method private async savePicture(photo: Photo) { // Fetch the photo, read as a blob, then convert to base64 format const response = await fetch(photo.webPath!); @@ -130,7 +130,7 @@ export class PhotoService { }; } - // CHANGE: Add the `convertBlobToBase64` method. + // CHANGE: Add the `convertBlobToBase64` method private convertBlobToBase64(blob: Blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); diff --git a/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md b/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md index 11d1546df8a..46b151aece7 100644 --- a/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md @@ -23,7 +23,7 @@ Open `photo.service.ts` and begin by defining a new property in the `PhotoServic export class PhotoService { public photos: UserPhoto[] = []; - // CHANGE: Add a key for photo storage. + // CHANGE: Add a key for photo storage private PHOTO_STORAGE: string = 'photos'; // ...existing code... @@ -45,7 +45,7 @@ public async addNewToGallery() { this.photos.unshift(savedImageFile); - // CHANGE: Add 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), @@ -59,7 +59,7 @@ With the photo array data saved, create a new public method in the `PhotoService export class PhotoService { // ...existing code... - // CHANGE: Add the method to load the photo data. + // CHANGE: Add the method to load the photo data public async loadSaved() { // Retrieve cached photo array data const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); @@ -74,13 +74,13 @@ On mobile (coming up next!), we can directly set the source of an image tag - `< export class PhotoService { // ...existing code... - // CHANGE: Update the `loadSaved` method. + // CHANGE: Update the `loadSaved()` method public async loadSaved() { // Retrieve cached photo array data const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; - // CHANGE: Display the photo by reading into base64 format. + // 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 file = await Filesystem.file({ @@ -175,7 +175,7 @@ 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 the Photo Gallery, all photos are loaded and displayed on the screen. +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 the Photo Gallery, all photos are loaded and displayed on the screen. Update `tab2.page.ts` to look like the following: @@ -192,7 +192,7 @@ import { PhotoService } from '../services/photo.service'; export class Tab2Page { constructor(public photoService: PhotoService) {} - // CHANGE: Add call to `loadSaved` when navigating to the Photos tab. + // CHANGE: Add call to `loadSaved()` when navigating to the Photos tab async ngOnInit() { await this.photoService.loadSaved(); } diff --git a/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md b/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md index c8ee387b212..b54e45c619e 100644 --- a/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md +++ b/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md @@ -26,7 +26,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'; -// CHANGE: Add import. +// CHANGE: Add import import { Platform } from '@ionic/angular'; export class PhotoService { @@ -34,10 +34,10 @@ export class PhotoService { private PHOTO_STORAGE: string = 'photos'; - // CHANGE: Add a property to track the app's running platform. + // CHANGE: Add a property to track the app's running platform private platform: Platform; - // CHANGE: Update constructor to set `platform`. + // CHANGE: Update constructor to set `platform` constructor(platform: Platform) { this.platform = platform; } @@ -53,11 +53,11 @@ First, we’ll update the photo saving functionality to support mobile. In the ` Update `savePicture()` to look like the following: ```ts -// CHANGE: Update the `savePicture()` method. +// CHANGE: Update the `savePicture()` method private async savePicture(photo: Photo) { let base64Data: string | Blob; - // CHANGE: Add platform check. + // CHANGE: Add platform check // "hybrid" will detect Cordova or Capacitor if (this.platform.is('hybrid')) { // Read the file into base64 format @@ -98,7 +98,7 @@ import { Capacitor } from '@capacitor/core'; Then update `savePicture()` to look like the following: ```ts -// CHANGE: Update `savePicture()` method. +// CHANGE: Update `savePicture()` method private async savePicture(photo: Photo) { let base64Data: string | Blob; // "hybrid" will detect mobile - iOS or Android @@ -122,7 +122,7 @@ private async savePicture(photo: Photo) { directory: Directory.Data, }); - // CHANGE: Add platform check. + // CHANGE: Add platform check if (this.platform.is('hybrid')) { // Display the new image by rewriting the 'file://' path to HTTP return { @@ -143,12 +143,12 @@ private async savePicture(photo: Photo) { Next, add a new bit of logic in the `loadSaved()` method. On mobile, we can directly point to each photo file on the Filesystem and display them automatically. On the web, however, we must read each image from the Filesystem into base64 format. This is because the Filesystem API uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood. Update the `loadSaved()` method: ```ts -// CHANGE: Update `loadSaved` method. +// CHANGE: Update `loadSaved()` method. public async loadSaved() { const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; - // CHANGE: Add platform check. + // CHANGE: Add platform check // If running on the web... if (!this.platform.is('hybrid')) { for (let photo of this.photos) { diff --git a/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md b/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md index 500990df1c7..6c3b631981f 100644 --- a/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md +++ b/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md @@ -51,7 +51,7 @@ import { Capacitor } from '@capacitor/core'; export class PhotoService { // ...existing code... - // CHANGE: Add `deletePhoto()` method. + // CHANGE: Add `deletePhoto()` method public async deletePhoto(photo: UserPhoto, position: number) { // Remove this photo from the Photos reference data array this.photos.splice(position, 1); @@ -82,10 +82,10 @@ Next, in `tab2.page.ts`, implement the `showActionSheet()` method. We're adding ```ts import { Component } from '@angular/core'; -// Change: Add import. +// Change: Add import import type { UserPhoto } from '../services/photo.service'; import { PhotoService } from '../services/photo.service'; -// CHANGE: Add import. +// CHANGE: Add import import { ActionSheetController } from '@ionic/angular'; @Component({ @@ -95,12 +95,12 @@ import { ActionSheetController } from '@ionic/angular'; standalone: false, }) export class Tab2Page { - // CHANGE: Update constructor. + // CHANGE: Update constructor constructor(public photoService: PhotoService, public actionSheetController: ActionSheetController) {} // ...existing code... - // CHANGE: Add `showActionSheet` method. + // CHANGE: Add `showActionSheet()` method public async showActionSheet(photo: UserPhoto, position: number) { const actionSheet = await this.actionSheetController.create({ header: 'Photos', From 3139c48258bea2d42ffa403436202fb9fc1b3fa5 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 30 Oct 2025 17:23:56 -0700 Subject: [PATCH 60/68] docs(angular): move note --- docs/angular/your-first-app/6-deploying-mobile.md | 4 ---- docs/angular/your-first-app/7-live-reload.md | 4 ++++ .../version-v7/angular/your-first-app/6-deploying-mobile.md | 4 ---- .../version-v7/angular/your-first-app/7-live-reload.md | 4 ++++ 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/angular/your-first-app/6-deploying-mobile.md b/docs/angular/your-first-app/6-deploying-mobile.md index 3497cad7fba..bcbb63ae6e5 100644 --- a/docs/angular/your-first-app/6-deploying-mobile.md +++ b/docs/angular/your-first-app/6-deploying-mobile.md @@ -13,10 +13,6 @@ sidebar_label: Deploying Mobile Since we added Capacitor to our project when it was first created, there’s only a handful of steps remaining until the Photo Gallery app is on our device! -:::note -Remember, you can find the complete source code for this app [here](https://github.com/ionic-team/photo-gallery-capacitor-ng). -::: - ## Capacitor Setup Capacitor is Ionic’s official app runtime that makes it easy to deploy web apps to native platforms like iOS, Android, and more. If you’ve used Cordova in the past, consider reading more about the differences [here](https://capacitorjs.com/docs/cordova#differences-between-capacitor-and-cordova). diff --git a/docs/angular/your-first-app/7-live-reload.md b/docs/angular/your-first-app/7-live-reload.md index 6c3b631981f..b008d8b6144 100644 --- a/docs/angular/your-first-app/7-live-reload.md +++ b/docs/angular/your-first-app/7-live-reload.md @@ -163,4 +163,8 @@ Open `tab2.page.html` and add a new click handler to each `` element. W Tap on a photo again and choose the “Delete” option. The photo is deleted! Implemented much faster using Live Reload. 💪 +:::note +Remember, you can find the complete source code for this app [here](https://github.com/ionic-team/photo-gallery-capacitor-ng). +::: + In the final portion of this tutorial, we’ll walk you through the basics of the Appflow product used to build and deploy your application to users' devices. diff --git a/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md b/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md index 3497cad7fba..bcbb63ae6e5 100644 --- a/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md +++ b/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md @@ -13,10 +13,6 @@ sidebar_label: Deploying Mobile Since we added Capacitor to our project when it was first created, there’s only a handful of steps remaining until the Photo Gallery app is on our device! -:::note -Remember, you can find the complete source code for this app [here](https://github.com/ionic-team/photo-gallery-capacitor-ng). -::: - ## Capacitor Setup Capacitor is Ionic’s official app runtime that makes it easy to deploy web apps to native platforms like iOS, Android, and more. If you’ve used Cordova in the past, consider reading more about the differences [here](https://capacitorjs.com/docs/cordova#differences-between-capacitor-and-cordova). diff --git a/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md b/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md index 6c3b631981f..b008d8b6144 100644 --- a/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md +++ b/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md @@ -163,4 +163,8 @@ Open `tab2.page.html` and add a new click handler to each `` element. W Tap on a photo again and choose the “Delete” option. The photo is deleted! Implemented much faster using Live Reload. 💪 +:::note +Remember, you can find the complete source code for this app [here](https://github.com/ionic-team/photo-gallery-capacitor-ng). +::: + In the final portion of this tutorial, we’ll walk you through the basics of the Appflow product used to build and deploy your application to users' devices. From 7e46c173b9ccfababa859553acac9543c6a44d67 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 30 Oct 2025 17:26:30 -0700 Subject: [PATCH 61/68] docs(angular): add deployment word --- docs/angular/your-first-app/6-deploying-mobile.md | 4 ++-- .../version-v7/angular/your-first-app/6-deploying-mobile.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/angular/your-first-app/6-deploying-mobile.md b/docs/angular/your-first-app/6-deploying-mobile.md index bcbb63ae6e5..f2f8956d077 100644 --- a/docs/angular/your-first-app/6-deploying-mobile.md +++ b/docs/angular/your-first-app/6-deploying-mobile.md @@ -44,7 +44,7 @@ Note: After making updates to the native portion of the code (such as adding a n ionic cap sync ``` -## iOS +## iOS Deployment :::note To build an iOS app, you’ll need a Mac computer. @@ -80,7 +80,7 @@ Upon tapping the Camera button on the Photo Gallery tab, the permission prompt w ![Two iPhones side by side, one showing the camera permission prompt and the other displaying a photo taken with the app.](/img/guides/first-app-cap-ng/ios-permissions-photo.png 'iOS Camera Permission Prompt and Photo Result') -## Android +## Android Deployment Capacitor Android apps are configured and managed through Android Studio. Before running this app on an Android device, there's a couple of steps to complete. diff --git a/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md b/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md index bcbb63ae6e5..f2f8956d077 100644 --- a/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md +++ b/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md @@ -44,7 +44,7 @@ Note: After making updates to the native portion of the code (such as adding a n ionic cap sync ``` -## iOS +## iOS Deployment :::note To build an iOS app, you’ll need a Mac computer. @@ -80,7 +80,7 @@ Upon tapping the Camera button on the Photo Gallery tab, the permission prompt w ![Two iPhones side by side, one showing the camera permission prompt and the other displaying a photo taken with the app.](/img/guides/first-app-cap-ng/ios-permissions-photo.png 'iOS Camera Permission Prompt and Photo Result') -## Android +## Android Deployment Capacitor Android apps are configured and managed through Android Studio. Before running this app on an Android device, there's a couple of steps to complete. From 1122ed6b2df71d8cd5e31c6b0e6a3936b8020075 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 30 Oct 2025 17:30:14 -0700 Subject: [PATCH 62/68] docs(angular): change to important --- docs/angular/your-first-app/6-deploying-mobile.md | 2 +- .../version-v7/angular/your-first-app/6-deploying-mobile.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/angular/your-first-app/6-deploying-mobile.md b/docs/angular/your-first-app/6-deploying-mobile.md index f2f8956d077..3630ea09fca 100644 --- a/docs/angular/your-first-app/6-deploying-mobile.md +++ b/docs/angular/your-first-app/6-deploying-mobile.md @@ -46,7 +46,7 @@ ionic cap sync ## iOS Deployment -:::note +:::important To build an iOS app, you’ll need a Mac computer. ::: diff --git a/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md b/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md index f2f8956d077..3630ea09fca 100644 --- a/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md +++ b/versioned_docs/version-v7/angular/your-first-app/6-deploying-mobile.md @@ -46,7 +46,7 @@ ionic cap sync ## iOS Deployment -:::note +:::important To build an iOS app, you’ll need a Mac computer. ::: From c0b36f1a818d372f3b2f7d382d7c9283229838b1 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 30 Oct 2025 17:35:23 -0700 Subject: [PATCH 63/68] Update docs/angular/your-first-app/7-live-reload.md Co-authored-by: Brandy Smith --- docs/angular/your-first-app/7-live-reload.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/angular/your-first-app/7-live-reload.md b/docs/angular/your-first-app/7-live-reload.md index b008d8b6144..3137800bd1d 100644 --- a/docs/angular/your-first-app/7-live-reload.md +++ b/docs/angular/your-first-app/7-live-reload.md @@ -78,7 +78,7 @@ export interface UserPhoto { } ``` -Next, in `tab2.page.ts`, implement the `showActionSheet()` method. We're adding two options: "Delete", which calls `PhotoService.deletePhoto()`, and "Cancel". The cancel button will automatically closes the action sheet when assigned the "cancel" role. +Next, in `tab2.page.ts`, implement the `showActionSheet()` method. We're adding two options: "Delete", which calls `PhotoService.deletePhoto()`, and "Cancel". The cancel button will automatically close the action sheet when assigned the "cancel" role. ```ts import { Component } from '@angular/core'; From a55294ff085afe6225dad044f90ec076670e55a7 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Thu, 30 Oct 2025 17:36:19 -0700 Subject: [PATCH 64/68] Update docs/angular/your-first-app/5-adding-mobile.md Co-authored-by: Brandy Smith --- 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 56f57952cb5..82d7372bf3b 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -48,7 +48,7 @@ export class PhotoService { ## Platform-specific Logic -First, we’ll update the photo saving functionality to support mobile. In the `savePicture()` method, check which platform the app is running on. If it’s “hybrid” (Capacitor, the native runtime), then read the photo file into base64 format using the `Filesystem`'s' `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 `savePicture()` method, check which platform the app is running on. If it’s “hybrid” (Capacitor, the native runtime), 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 `savePicture()` to look like the following: From 68ca2c79bf12f83e59eca24fea9d117ef0ef5e23 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 5 Nov 2025 13:19:19 -0800 Subject: [PATCH 65/68] docs(angular): add missing code Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> --- docs/angular/your-first-app/2-taking-photos.md | 3 +++ docs/angular/your-first-app/5-adding-mobile.md | 3 +++ .../version-v7/angular/your-first-app/2-taking-photos.md | 3 +++ .../version-v7/angular/your-first-app/5-adding-mobile.md | 3 +++ 4 files changed, 12 insertions(+) diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 0acdc5b824b..0e53d2d2bd6 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -44,6 +44,9 @@ 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 { // CHANGE: Add the gallery method public async addNewToGallery() { diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 56f57952cb5..bbaa839dbdd 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -29,6 +29,9 @@ import { Preferences } from '@capacitor/preferences'; // CHANGE: Add import import { Platform } from '@ionic/angular'; +@Injectable({ + providedIn: 'root', +}) export class PhotoService { public photos: UserPhoto[] = []; diff --git a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md index 0acdc5b824b..0e53d2d2bd6 100644 --- a/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/2-taking-photos.md @@ -44,6 +44,9 @@ 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 { // CHANGE: Add the gallery method public async addNewToGallery() { diff --git a/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md b/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md index b54e45c619e..63d631e24bb 100644 --- a/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md +++ b/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md @@ -29,6 +29,9 @@ import { Preferences } from '@capacitor/preferences'; // CHANGE: Add import import { Platform } from '@ionic/angular'; +@Injectable({ + providedIn: 'root', +}) export class PhotoService { public photos: UserPhoto[] = []; From a2e108de9c36e47bcb71be6818c7611bd478d745 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 5 Nov 2025 13:24:19 -0800 Subject: [PATCH 66/68] docs(angular): use correct variable Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> --- docs/angular/your-first-app/4-loading-photos.md | 2 +- docs/angular/your-first-app/5-adding-mobile.md | 8 ++++---- .../version-v7/angular/your-first-app/4-loading-photos.md | 2 +- .../version-v7/angular/your-first-app/5-adding-mobile.md | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index 46b151aece7..901407e4a47 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -83,7 +83,7 @@ export class PhotoService { // 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 file = await Filesystem.file({ + const readFile = await Filesystem.readFile({ path: photo.filepath, directory: Directory.Data, }); diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index 8f1d7983fed..13fa86a6e28 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -155,13 +155,13 @@ public async loadSaved() { // If running on the web... if (!this.platform.is('hybrid')) { for (let photo of this.photos) { - const file = await Filesystem.file({ + 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,${file.data}`; + photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; } } } @@ -273,12 +273,12 @@ export class PhotoService { // If running on the web... if (!this.platform.is('hybrid')) { for (let photo of this.photos) { - const file = await Filesystem.file({ + 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,${file.data}`; + photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; } } } diff --git a/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md b/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md index 46b151aece7..901407e4a47 100644 --- a/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md @@ -83,7 +83,7 @@ export class PhotoService { // 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 file = await Filesystem.file({ + const readFile = await Filesystem.readFile({ path: photo.filepath, directory: Directory.Data, }); diff --git a/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md b/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md index 63d631e24bb..4b3c87a2dea 100644 --- a/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md +++ b/versioned_docs/version-v7/angular/your-first-app/5-adding-mobile.md @@ -155,13 +155,13 @@ public async loadSaved() { // If running on the web... if (!this.platform.is('hybrid')) { for (let photo of this.photos) { - const file = await Filesystem.file({ + 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,${file.data}`; + photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; } } } @@ -273,12 +273,12 @@ export class PhotoService { // If running on the web... if (!this.platform.is('hybrid')) { for (let photo of this.photos) { - const file = await Filesystem.file({ + 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,${file.data}`; + photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; } } } From 86f805e4e76dfbf5c34b552897daabcd0cf78b45 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 5 Nov 2025 13:26:30 -0800 Subject: [PATCH 67/68] docs(angular): replace substr Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> --- docs/angular/your-first-app/7-live-reload.md | 2 +- .../version-v7/angular/your-first-app/7-live-reload.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/angular/your-first-app/7-live-reload.md b/docs/angular/your-first-app/7-live-reload.md index 3137800bd1d..ea6a2703666 100644 --- a/docs/angular/your-first-app/7-live-reload.md +++ b/docs/angular/your-first-app/7-live-reload.md @@ -63,7 +63,7 @@ export class PhotoService { }); // Delete photo file from filesystem - const filename = photo.filepath.substr(photo.filepath.lastIndexOf('/') + 1); + const filename = photo.filepath.slice(photo.filepath.lastIndexOf('/') + 1); await Filesystem.deleteFile({ path: filename, diff --git a/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md b/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md index b008d8b6144..7545f7c617a 100644 --- a/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md +++ b/versioned_docs/version-v7/angular/your-first-app/7-live-reload.md @@ -63,7 +63,7 @@ export class PhotoService { }); // Delete photo file from filesystem - const filename = photo.filepath.substr(photo.filepath.lastIndexOf('/') + 1); + const filename = photo.filepath.slice(photo.filepath.lastIndexOf('/') + 1); await Filesystem.deleteFile({ path: filename, From db3959bf935815f1ff0ba79df7e6e51c4baf881e Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Wed, 5 Nov 2025 13:37:05 -0800 Subject: [PATCH 68/68] docs(angular): add missing code for loadSaved --- docs/angular/your-first-app/4-loading-photos.md | 12 ++++++++++++ .../angular/your-first-app/4-loading-photos.md | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index 901407e4a47..ce53a19860c 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -119,6 +119,7 @@ export class PhotoService { quality: 100, }); + // Save the picture and add it to photo collection const savedImageFile = await this.savePicture(capturedPhoto); this.photos.unshift(savedImageFile); @@ -166,6 +167,17 @@ export class PhotoService { // Retrieve cached photo array data const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; + + 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}`; + } } } diff --git a/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md b/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md index 901407e4a47..ce53a19860c 100644 --- a/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md +++ b/versioned_docs/version-v7/angular/your-first-app/4-loading-photos.md @@ -119,6 +119,7 @@ export class PhotoService { quality: 100, }); + // Save the picture and add it to photo collection const savedImageFile = await this.savePicture(capturedPhoto); this.photos.unshift(savedImageFile); @@ -166,6 +167,17 @@ export class PhotoService { // Retrieve cached photo array data const { value: photoList } = await Preferences.get({ key: this.PHOTO_STORAGE }); this.photos = (photoList ? JSON.parse(photoList) : []) as UserPhoto[]; + + 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}`; + } } }