Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions docs/angular/your-first-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ ionic start photo-gallery tabs --type=angular

:::note

When prompted to choose between `NgModules` and `Standalone`, opt for `NgModules` as this tutorial follows the `NgModules` approach.
When prompted to choose between `NgModules` and `Standalone`, choose `Standalone` as this tutorial follows the standalone components approach.

:::

Expand Down Expand Up @@ -109,17 +109,25 @@ npm install @ionic/pwa-elements
Next, import `@ionic/pwa-elements` by editing `src/main.ts`.

```ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { bootstrapApplication } from '@angular/platform-browser';
import { RouteReuseStrategy, provideRouter, withPreloading, PreloadAllModules } from '@angular/router';
import { IonicRouteStrategy, provideIonicAngular } from '@ionic/angular';
// CHANGE: Add the following import
import { defineCustomElements } from '@ionic/pwa-elements/loader';

// CHANGE: Call the element loader before the `bootstrapModule` call
import { routes } from './app/app.routes';
import { AppComponent } from './app/app.component';

// CHANGE: Call the element loader before the `bootstrapApplication` call
defineCustomElements(window);

platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.log(err));
bootstrapApplication(AppComponent, {
providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
provideIonicAngular(),
provideRouter(routes, withPreloading(PreloadAllModules)),
],
}).catch((err) => console.error(err));
```

That’s it! Now for the fun part - let’s see the app in action.
Expand Down
91 changes: 69 additions & 22 deletions docs/angular/your-first-app/2-taking-photos.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,66 @@ Notice the magic here: there's no platform-specific code (web, iOS, or Android)!
Next, in `tab2.page.ts`, import the `PhotoService` class and add a method to call its `addNewToGallery` method.

```ts
import { Component } from '@angular/core';
import { Component, inject } from '@angular/core';
// CHANGE: Import the Ionic standalone components used on this page
import {
IonHeader,
IonToolbar,
IonTitle,
IonContent,
IonGrid,
IonRow,
IonCol,
IonImg,
IonFab,
IonFabButton,
IonIcon,
} from '@ionic/angular';
// CHANGE: Register the camera icon used by the FAB
import { addIcons } from 'ionicons';
import { camera } from 'ionicons/icons';
// CHANGE: Import the PhotoService
import { PhotoService } from '../services/photo.service';

@Component({
selector: 'app-tab2',
templateUrl: 'tab2.page.html',
styleUrls: ['tab2.page.scss'],
standalone: false,
// CHANGE: Add the standalone component imports
imports: [
IonHeader,
IonToolbar,
IonTitle,
IonContent,
IonGrid,
IonRow,
IonCol,
IonImg,
IonFab,
IonFabButton,
IonIcon,
],
})
export class Tab2Page {
// CHANGE: Update constructor to include `photoService`
constructor(public photoService: PhotoService) {}
// CHANGE: Inject the PhotoService
public photoService = inject(PhotoService);

// CHANGE: Add `addNewToGallery()` method
constructor() {
// CHANGE: Register the icons this page uses
addIcons({ camera });
}

// CHANGE: Add `addPhotoToGallery()` method
addPhotoToGallery() {
this.photoService.addNewToGallery();
}
}
```

:::note
In a standalone app there is no global icon registry, so each icon you reference by name (like `camera`) must be registered with `addIcons`. Import the specific Ionic components a page uses from `@ionic/angular` and list them in the component's `imports` array.
:::

Then, open `tab2.page.html` and call the `addPhotoToGallery()` method when the FAB is tapped/clicked:

```html
Expand Down Expand Up @@ -131,20 +170,20 @@ export interface UserPhoto {
}
```

Above the `addNewToGallery()` method, define an array of `UserPhoto`, which will contain a reference to each photo captured with the Camera.
Above the `addNewToGallery()` method, define a [signal](https://angular.dev/guide/signals) that holds an array of `UserPhoto`, which will contain a reference to each photo captured with the Camera. A signal is used so that the gallery view updates automatically when photos change - important in a zoneless app, where mutating a plain array would not trigger a re-render.

```ts
export class PhotoService {
// CHANGE: Add the `photos` array
public photos: UserPhoto[] = [];
// CHANGE: Add the `photos` signal
public photos = signal<UserPhoto[]>([]);

public async addNewToGallery() {
// ...existing code...
}
}
```

Over in the `addNewToGallery` method, add the newly captured photo to the beginning of the `photos` array.
Over in the `addNewToGallery` method, add the newly captured photo to the beginning of the `photos` signal. Reading and updating a signal is done by calling it: `this.photos()` returns the current value, and `this.photos.update()` sets a new one.

```ts
// CHANGE: Update `addNewToGallery()` method
Expand All @@ -156,25 +195,28 @@ public async addNewToGallery() {
quality: 100
});

// CHANGE: Add the new photo to the photos array
this.photos.unshift({
filepath: "soon...",
webviewPath: capturedPhoto.webPath!
});
// CHANGE: Add the new photo to the front of the photos signal
this.photos.update((photos) => [
{
filepath: 'soon...',
webviewPath: capturedPhoto.webPath!,
},
...photos,
]);
}
```

`photo.service.ts` should now look like this:

```ts
import { Injectable } from '@angular/core';
import { Injectable, signal } from '@angular/core';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';

@Injectable({
providedIn: 'root',
})
export class PhotoService {
public photos: UserPhoto[] = [];
public photos = signal<UserPhoto[]>([]);

public async addNewToGallery() {
// Take a photo
Expand All @@ -184,10 +226,13 @@ export class PhotoService {
quality: 100,
});

this.photos.unshift({
filepath: 'soon...',
webviewPath: capturedPhoto.webPath!,
});
this.photos.update((photos) => [
{
filepath: 'soon...',
webviewPath: capturedPhoto.webPath!,
},
...photos,
]);
}
}

Expand All @@ -197,7 +242,7 @@ export interface UserPhoto {
}
```

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.
Next, switch to `tab2.page.html` to display the images. We'll add a [Grid component](../../api/grid.md) so the photos display neatly as they're added to the gallery. Inside the grid, loop through each photo in the `PhotoService`'s `photos` signal with the built-in [`@for`](https://angular.dev/guide/templates/control-flow#for-block-repeaters) block - calling `photoService.photos()` reads the signal's current value. For each item, add an [Image component](../../api/img.md) and set its `src` property to the photo's path.

```html
<ion-header [translucent]="true">
Expand All @@ -217,9 +262,11 @@ Next, switch to `tab2.page.html` to display the images. We'll add a [Grid compon
<ion-grid>
<ion-row>
<!-- CHANGE: Create a new column and image component for each photo -->
<ion-col size="6" *ngFor="let photo of photoService.photos; index as position">
@for (photo of photoService.photos(); track photo.webviewPath; let position = $index) {
<ion-col size="6">
<ion-img [src]="photo.webviewPath"></ion-img>
</ion-col>
}
</ion-row>
</ion-grid>

Expand Down
14 changes: 7 additions & 7 deletions docs/angular/your-first-app/3-saving-photos.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ export interface UserPhoto {
We can use this new method immediately in `addNewToGallery()`.

```ts
import { Injectable } from '@angular/core';
import { Injectable, signal } from '@angular/core';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';

@Injectable({
providedIn: 'root',
})
export class PhotoService {
public photos: UserPhoto[] = [];
public photos = signal<UserPhoto[]>([]);

// CHANGE: Update the `addNewToGallery()` method
public async addNewToGallery() {
Expand All @@ -68,8 +68,8 @@ export class PhotoService {
// 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);
// CHANGE: Add the saved photo to the front of the photos signal
this.photos.update((photos) => [savedImageFile, ...photos]);
}

private async savePicture(photo: Photo) {
Expand Down Expand Up @@ -150,7 +150,7 @@ export interface UserPhoto {
`photo.service.ts` should now look like this:

```ts
import { Injectable } from '@angular/core';
import { Injectable, signal } from '@angular/core';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import type { Photo } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';
Expand All @@ -159,7 +159,7 @@ import { Filesystem, Directory } from '@capacitor/filesystem';
providedIn: 'root',
})
export class PhotoService {
public photos: UserPhoto[] = [];
public photos = signal<UserPhoto[]>([]);

public async addNewToGallery() {
// Take a photo
Expand All @@ -172,7 +172,7 @@ export class PhotoService {
// Save the picture and add it to photo collection
const savedImageFile = await this.savePicture(capturedPhoto);

this.photos.unshift(savedImageFile);
this.photos.update((photos) => [savedImageFile, ...photos]);
}

private async savePicture(photo: Photo) {
Expand Down
Loading
Loading