diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md
index 739a9c3d29..593a12582a 100644
--- a/docs/angular/your-first-app.md
+++ b/docs/angular/your-first-app.md
@@ -4,10 +4,10 @@ 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
@@ -34,11 +34,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 +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
@@ -71,7 +70,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
@@ -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](../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:
@@ -109,11 +108,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.
@@ -128,55 +134,89 @@ 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!
+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!

-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/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md
index 0e0cd078a7..0e53d2d2bd 100644
--- a/docs/angular/your-first-app/2-taking-photos.md
+++ b/docs/angular/your-first-app/2-taking-photos.md
@@ -4,63 +4,106 @@ 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).
+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';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class PhotoService {
+ // CHANGE: Add the gallery method
+ 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.
-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 +111,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!

@@ -78,34 +121,46 @@ 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:
+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 {
+ // ...existing code...
+}
-```tsx
+// 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() {
+ // ...existing code...
+ }
}
```
-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 +168,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/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md
index c1e013a8be..5c9d3ccd9e 100644
--- a/docs/angular/your-first-app/3-saving-photos.md
+++ b/docs/angular/your-first-app/3-saving-photos.md
@@ -1,81 +1,222 @@
---
+title: Saving Photos to the Filesystem
sidebar_label: Saving Photos
---
-# Saving Photos to the Filesystem
+
+ Saving Photos to the Filesystem with Angular | Ionic Capacitor Camera
+
+
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 {
+ // ...existing code...
+
+ // 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 {
+ // ...existing code...
+
+ // 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/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md
index 6f59d3f951..ce53a19860 100644
--- a/docs/angular/your-first-app/4-loading-photos.md
+++ b/docs/angular/your-first-app/4-loading-photos.md
@@ -1,69 +1,224 @@
---
+title: Loading Photos from the Filesystem
sidebar_label: Loading Photos
---
-# Loading Photos from the Filesystem
+
+ Loading Photos from the Filesystem with Angular | Ionic Capacitor Camera
+
+
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
+ // ...existing 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.
+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,
+ });
+
+ const savedImageFile = await this.savePicture(capturedPhoto);
-```tsx
-Preferences.set({
- key: this.PHOTO_STORAGE,
- value: JSON.stringify(this.photos),
-});
+ 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 {
+ // ...existing code...
+
+ // 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 {
+ // ...existing code...
+
+ // 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 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}`;
+ }
+ }
}
```
-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,
+ });
+
+ // Save the picture and add it to photo collection
+ 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[];
+
+ 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 {
+ 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/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md
index ce7f60e75c..13fa86a6e2 100644
--- a/docs/angular/your-first-app/5-adding-mobile.md
+++ b/docs/angular/your-first-app/5-adding-mobile.md
@@ -1,8 +1,15 @@
---
+title: Adding Mobile
strip_number_prefixes: false
---
-# Adding Mobile
+
+ Adding Mobile Support with Angular | Ionic Capacitor Camera
+
+
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,98 +17,144 @@ 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).
-```tsx
+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.
+
+```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';
+@Injectable({
+ providedIn: 'root',
+})
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
+ // ...existing 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 `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:
+
+```ts
+// CHANGE: Update the `savePicture()` method
+private async savePicture(photo: Photo) {
+ let base64Data: string | Blob;
-```tsx
-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
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`.
+
+```ts
+import { Capacitor } from '@capacitor/core';
+```
+
+Then update `savePicture()` to look like the following:
-```tsx
-// Save picture to file on device
+```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({
path: photo.filepath,
directory: Directory.Data
@@ -114,4 +167,127 @@ 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:
+
+```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 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 {
+ filepath: string;
+ webviewPath?: string;
+}
+```
+
+Next up, the part you’ve been waiting for - deploying the app to a device.
diff --git a/docs/angular/your-first-app/6-deploying-mobile.md b/docs/angular/your-first-app/6-deploying-mobile.md
index e9637084b7..3630ea09fc 100644
--- a/docs/angular/your-first-app/6-deploying-mobile.md
+++ b/docs/angular/your-first-app/6-deploying-mobile.md
@@ -4,20 +4,20 @@ 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!
## 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 +26,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.).
@@ -46,7 +46,7 @@ ionic cap sync
## iOS Deployment
-:::note
+:::important
To build an iOS app, you’ll need a Mac computer.
:::
@@ -58,7 +58,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."

@@ -111,4 +111,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 e6d0afe17f..ea6a270366 100644
--- a/docs/angular/your-first-app/7-live-reload.md
+++ b/docs/angular/your-first-app/7-live-reload.md
@@ -4,6 +4,7 @@ sidebar_label: Live Reload
---
+ Rapid App Development with Live Reload with Angular | Ionic Capacitor Camera
` 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 {
+ // ...existing code...
+
+ // 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.slice(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 close 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) {}
+
+ // ...existing code...
+
+ // 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.
+Tap on a photo again and choose the “Delete” option. The photo is deleted! Implemented much faster using Live Reload. 💪
-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. 💪
+:::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/docs/angular/your-first-app/8-distribute.md b/docs/angular/your-first-app/8-distribute.md
index 65ef475f6b..bf495f6842 100644
--- a/docs/angular/your-first-app/8-distribute.md
+++ b/docs/angular/your-first-app/8-distribute.md
@@ -1,8 +1,15 @@
---
+title: Build and Distribute your App
sidebar_label: Distribute
---
-# Build and Deploy your App
+
+ Build and Deploy your App with Angular | Ionic Capacitor Camera
+
+
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 +59,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 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!
@@ -93,8 +100,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! 💙
diff --git a/versioned_docs/version-v7/angular/your-first-app.md b/versioned_docs/version-v7/angular/your-first-app.md
index bfc88a1c47..593a12582a 100644
--- a/versioned_docs/version-v7/angular/your-first-app.md
+++ b/versioned_docs/version-v7/angular/your-first-app.md
@@ -4,10 +4,10 @@ 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
@@ -19,9 +19,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 +34,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 +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
@@ -71,12 +70,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 +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](../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 +108,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.
@@ -122,55 +134,89 @@ 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!
+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!

-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 0e0cd078a7..0e53d2d2bd 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,106 @@ 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).
+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';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class PhotoService {
+ // CHANGE: Add the gallery method
+ 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.
-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 +111,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!

@@ -78,34 +121,46 @@ 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:
+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 {
+ // ...existing code...
+}
-```tsx
+// 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() {
+ // ...existing code...
+ }
}
```
-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 +168,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 c1e013a8be..5c9d3ccd9e 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,222 @@
---
+title: Saving Photos to the Filesystem
sidebar_label: Saving Photos
---
-# Saving Photos to the Filesystem
+
+ Saving Photos to the Filesystem with Angular | Ionic Capacitor Camera
+
+
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 {
+ // ...existing code...
+
+ // 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 {
+ // ...existing code...
+
+ // 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 6f59d3f951..ce53a19860 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,224 @@
---
+title: Loading Photos from the Filesystem
sidebar_label: Loading Photos
---
-# Loading Photos from the Filesystem
+
+ Loading Photos from the Filesystem with Angular | Ionic Capacitor Camera
+
+
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
+ // ...existing 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.
+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,
+ });
+
+ const savedImageFile = await this.savePicture(capturedPhoto);
-```tsx
-Preferences.set({
- key: this.PHOTO_STORAGE,
- value: JSON.stringify(this.photos),
-});
+ 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 {
+ // ...existing code...
+
+ // 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 {
+ // ...existing code...
+
+ // 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 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}`;
+ }
+ }
}
```
-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,
+ });
+
+ // Save the picture and add it to photo collection
+ 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[];
+
+ 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 {
+ 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 ce7f60e75c..4b3c87a2de 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,8 +1,15 @@
---
+title: Adding Mobile
strip_number_prefixes: false
---
-# Adding Mobile
+
+ Adding Mobile Support with Angular | Ionic Capacitor Camera
+
+
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,98 +17,144 @@ 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).
-```tsx
+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.
+
+```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';
+@Injectable({
+ providedIn: 'root',
+})
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
+ // ...existing 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 `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:
+
+```ts
+// CHANGE: Update the `savePicture()` method
+private async savePicture(photo: Photo) {
+ let base64Data: string | Blob;
-```tsx
-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
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`.
+
+```ts
+import { Capacitor } from '@capacitor/core';
+```
+
+Then update `savePicture()` to look like the following:
-```tsx
-// Save picture to file on device
+```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({
path: photo.filepath,
directory: Directory.Data
@@ -114,4 +167,127 @@ 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:
+
+```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 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 {
+ filepath: string;
+ webviewPath?: string;
+}
+```
+
+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 e9637084b7..3630ea09fc 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,20 @@ 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!
## 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 +26,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.).
@@ -46,7 +46,7 @@ ionic cap sync
## iOS Deployment
-:::note
+:::important
To build an iOS app, you’ll need a Mac computer.
:::
@@ -58,7 +58,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."

@@ -111,4 +111,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 e6d0afe17f..7545f7c617 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,6 +4,7 @@ sidebar_label: Live Reload
---
+ Rapid App Development with Live Reload with Angular | Ionic Capacitor Camera
` 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 {
+ // ...existing code...
+
+ // 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.slice(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) {}
+
+ // ...existing code...
+
+ // 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.
+Tap on a photo again and choose the “Delete” option. The photo is deleted! Implemented much faster using Live Reload. 💪
-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. 💪
+:::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/8-distribute.md b/versioned_docs/version-v7/angular/your-first-app/8-distribute.md
index 65ef475f6b..bf495f6842 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,8 +1,15 @@
---
+title: Build and Distribute your App
sidebar_label: Distribute
---
-# Build and Deploy your App
+
+ Build and Deploy your App with Angular | Ionic Capacitor Camera
+
+
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 +59,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 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!
@@ -93,8 +100,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! 💙