Examples
Six pasteable Ionic patterns: a tab-based shell, page navigation with params, a form with a toast, an HTTP client with auth, an infinite-scroll list, and the Capacitor camera. Ionic v7 with Angular standalone components.
Six working Ionic snippets
EXAMPLE
// 1) App shell with tabs
// app.routes.ts
// import { Routes } from '@angular/router';
// export const routes: Routes = [
// { path: '', loadComponent: () => import('./tabs/tabs.page').then(m => m.TabsPage),
// children: [
// { path: '', redirectTo: 'home', pathMatch: 'full' },
// { path: 'home', loadComponent: () => import('./home/home.page').then(m => m.HomePage) },
// { path: 'catalog', loadComponent: () => import('./catalog/catalog.page').then(m => m.CatalogPage) },
// { path: 'orders', loadComponent: () => import('./orders/orders.page').then(m => m.OrdersPage) },
// ]
// },
// ];
// tabs.page.html
// <ion-tabs>
// <ion-tab-bar slot='bottom'>
// <ion-tab-button tab='home'><ion-icon name='home'/><ion-label>Home</ion-label></ion-tab-button>
// <ion-tab-button tab='catalog'><ion-icon name='bag'/><ion-label>Catalog</ion-label></ion-tab-button>
// <ion-tab-button tab='orders'><ion-icon name='receipt'/><ion-label>Orders</ion-label></ion-tab-button>
// </ion-tab-bar>
// </ion-tabs>
// 2) Page navigation with params
// catalog.page.ts
// constructor(private router: Router) {}
// openProduct(sku: string) { this.router.navigate(['/products', sku]); }
//
// app.routes.ts
// { path: 'products/:sku', loadComponent: () => import('./product/product.page').then(m => m.ProductPage) }
//
// product.page.ts
// import { ActivatedRoute } from '@angular/router';
// constructor(private route: ActivatedRoute) {}
// sku = this.route.snapshot.paramMap.get('sku')!;
// 3) Form + toast
// import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
// import { ToastController } from '@ionic/angular/standalone';
//
// form = this.fb.group({
// email: ['', [Validators.required, Validators.email]],
// password: ['', [Validators.required, Validators.minLength(8)]],
// });
//
// async submit() {
// if (this.form.invalid) return;
// try {
// await this.api.login(this.form.getRawValue());
// (await this.toast.create({ message: 'Welcome back', duration: 2000, color: 'success' })).present();
// } catch (e: any) {
// (await this.toast.create({ message: e.message, duration: 3000, color: 'danger' })).present();
// }
// }
// 4) HTTP client with bearer auth
// @Injectable({ providedIn: 'root' })
// export class Api {
// constructor(private http: HttpClient, private secure: Secure) {}
//
// private async authHeaders() {
// const token = await this.secure.getToken();
// return { Authorization: 'Bearer ' + (token ?? '') };
// }
//
// async listOrders(): Promise<Order[]> {
// return firstValueFrom(this.http.get<Order[]>('/api/orders', { headers: await this.authHeaders() }));
// }
// }
// 5) Infinite scroll list
// <ion-content>
// <ion-list>
// <ion-item *ngFor='let o of orders'>
// <ion-label>{{ o.customer }} — {{ o.total | currency:'AUD' }}</ion-label>
// </ion-item>
// </ion-list>
// <ion-infinite-scroll (ionInfinite)='loadMore($event)'>
// <ion-infinite-scroll-content loadingText='Loading...'></ion-infinite-scroll-content>
// </ion-infinite-scroll>
// </ion-content>
//
// async loadMore(ev: any) {
// const next = await this.api.listOrders({ cursor: this.cursor });
// this.orders.push(...next.data);
// this.cursor = next.nextCursor;
// ev.target.complete();
// if (!this.cursor) ev.target.disabled = true;
// }
// 6) Capacitor camera
// import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
//
// async takePhoto() {
// const photo = await Camera.getPhoto({
// resultType: CameraResultType.DataUrl,
// source: CameraSource.Camera,
// quality: 80,
// });
// this.preview = photo.dataUrl;
// }
// 7) Decision matrix
// - Tabs -> ion-tabs with router children
// - Modal forms -> ModalController + ion-modal
// - Toasts / loading -> ToastController / LoadingController
// - Bottom-sheet -> ion-modal initialBreakpoint
// - Page-level scrolling -> ion-content; avoid nested scrolling containers
// 8) Pitfalls
// - Nested scroll containers inside ion-content
// - Forgetting 'await loading.present()' before async work
// - Manual style overrides that break theme variables; use --ion-color-* CSS variables
// - Storing tokens in localStorage; use Capacitor Preferences or secure storage
Why it matters
Use Ionic primitives (ion-content, ion-toast, ion-modal) instead of hand-rolling equivalents. They handle keyboard avoidance, safe area, ripple feedback, and dark mode automatically — and they look right on both iOS and Android without per-platform tweaks. Resist the urge to style around them; bend them with CSS variables instead.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…