Docs
/
Angular
Chapter 21

21 — Ionic Mobile (Angular + Capacitor)

What is Ionic?

Ionic is a cross-platform UI framework that lets you build mobile (iOS/Android) and web apps from a single Angular codebase. Capacitor bridges web code to native APIs.

Angular App → Ionic UI Components → Capacitor → Native iOS/Android
Ionic + CapacitorReact NativeFlutter
LanguageTypeScript/HTML/CSSJavaScript/JSXDart
UIWeb components (styled native-like)Native componentsCustom rendering
PerformanceGood (WebView)Good (native bridge)Excellent (compiled)
Code sharing90%+ with web~70% with webSeparate from web
Learning curveLow (if you know Angular)MediumMedium

Setup

# Install Ionic CLI
npm install -g @ionic/cli

# Create Ionic + Angular app
ionic start my-app blank --type=angular --capacitor

# Or add Ionic to existing Angular app
ng add @ionic/angular
npm install @capacitor/core @capacitor/cli
npx cap init my-app com.example.myapp

Project Structure

my-app/
├── src/
│   ├── app/
│   │   ├── tabs/                    # Tab-based navigation
│   │   │   ├── tabs.page.ts
│   │   │   └── tabs.routes.ts
│   │   ├── home/
│   │   │   ├── home.page.ts
│   │   │   └── home.page.html
│   │   ├── profile/
│   │   ├── settings/
│   │   └── services/
│   ├── theme/
│   │   └── variables.scss           # Ionic CSS variables (colors, fonts)
│   └── global.scss
├── android/                          # Native Android project (auto-generated)
├── ios/                              # Native iOS project (auto-generated)
├── capacitor.config.ts
├── ionic.config.json
└── package.json

Ionic Components

<!-- Header -->
<ion-header>
  <ion-toolbar color="primary">
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/home" />
    </ion-buttons>
    <ion-title>Profile</ion-title>
    <ion-buttons slot="end">
      <ion-button (click)="openSettings()">
        <ion-icon name="settings-outline" />
      </ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<!-- Content with pull-to-refresh -->
<ion-content>
  <ion-refresher slot="fixed" (ionRefresh)="doRefresh($event)">
    <ion-refresher-content />
  </ion-refresher>

  <!-- List -->
  <ion-list>
    @for (item of items; track item.id) {
      <ion-item-sliding>
        <ion-item [routerLink]="['/items', item.id]">
          <ion-avatar slot="start">
            <img [src]="item.avatar" />
          </ion-avatar>
          <ion-label>
            <h2>{{ item.name }}</h2>
            <p>{{ item.description }}</p>
          </ion-label>
          <ion-badge slot="end" color="success">{{ item.status }}</ion-badge>
        </ion-item>

        <!-- Swipe actions -->
        <ion-item-options side="end">
          <ion-item-option color="danger" (click)="delete(item)">
            <ion-icon name="trash" slot="icon-only" />
          </ion-item-option>
        </ion-item-options>
      </ion-item-sliding>
    }
  </ion-list>

  <!-- Infinite scroll -->
  <ion-infinite-scroll (ionInfinite)="loadMore($event)">
    <ion-infinite-scroll-content loadingSpinner="circles" />
  </ion-infinite-scroll>
</ion-content>

<!-- Tabs -->
<ion-tabs>
  <ion-tab-bar slot="bottom">
    <ion-tab-button tab="home">
      <ion-icon name="home-outline" />
      <ion-label>Home</ion-label>
    </ion-tab-button>
    <ion-tab-button tab="search">
      <ion-icon name="search-outline" />
      <ion-label>Search</ion-label>
    </ion-tab-button>
    <ion-tab-button tab="profile">
      <ion-icon name="person-outline" />
      <ion-label>Profile</ion-label>
    </ion-tab-button>
  </ion-tab-bar>
</ion-tabs>

Capacitor — Native API Access

# Add native platforms
npx cap add android
npx cap add ios

# Sync web assets to native
npx cap sync

# Open in native IDE
npx cap open android    # Opens Android Studio
npx cap open ios        # Opens Xcode

# Live reload on device
ionic cap run android --livereload --external
ionic cap run ios --livereload --external

Camera

npm install @capacitor/camera
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';

async takePhoto() {
  const image = await Camera.getPhoto({
    quality: 80,
    resultType: CameraResultType.Base64,
    source: CameraSource.Camera,         // Or CameraSource.Photos for gallery
    allowEditing: false,
  });

  this.photoBase64 = `data:image/jpeg;base64,${image.base64String}`;
}

Geolocation

npm install @capacitor/geolocation
import { Geolocation } from '@capacitor/geolocation';

async getLocation() {
  const pos = await Geolocation.getCurrentPosition();
  this.lat = pos.coords.latitude;
  this.lng = pos.coords.longitude;
}

// Watch position
const watchId = await Geolocation.watchPosition({}, (pos) => {
  if (pos) {
    console.log(pos.coords.latitude, pos.coords.longitude);
  }
});

// Stop watching
await Geolocation.clearWatch({ id: watchId });

Local Storage (Preferences)

npm install @capacitor/preferences
import { Preferences } from '@capacitor/preferences';

// Set
await Preferences.set({ key: 'token', value: 'abc123' });

// Get
const { value } = await Preferences.get({ key: 'token' });

// Remove
await Preferences.remove({ key: 'token' });

// Clear all
await Preferences.clear();

Push Notifications

npm install @capacitor/push-notifications
import { PushNotifications } from '@capacitor/push-notifications';

async registerPush() {
  const perm = await PushNotifications.requestPermissions();
  if (perm.receive === 'granted') {
    await PushNotifications.register();
  }

  PushNotifications.addListener('registration', token => {
    console.log('Push token:', token.value);
    // Send token to your backend
  });

  PushNotifications.addListener('pushNotificationReceived', notification => {
    console.log('Received:', notification);
  });

  PushNotifications.addListener('pushNotificationActionPerformed', action => {
    console.log('Tapped notification:', action.notification);
    // Navigate based on notification data
  });
}

Platform Detection

import { Platform } from '@ionic/angular';

@Component({ ... })
export class AppComponent {
  private platform = inject(Platform);

  isIos     = this.platform.is('ios');
  isAndroid = this.platform.is('android');
  isMobile  = this.platform.is('mobile');
  isDesktop = this.platform.is('desktop');
  isPwa     = this.platform.is('pwa');
}
@if (isIos) {
  <p>iOS-specific content</p>
}

Theming

// src/theme/variables.scss
:root {
  --ion-color-primary: #3880ff;
  --ion-color-secondary: #3dc2ff;
  --ion-color-tertiary: #5260ff;
  --ion-color-success: #2dd36f;
  --ion-color-warning: #ffc409;
  --ion-color-danger: #eb445a;

  --ion-font-family: 'Inter', sans-serif;
}

// Dark mode
@media (prefers-color-scheme: dark) {
  :root {
    --ion-background-color: #1a1a2e;
    --ion-text-color: #ffffff;
    --ion-color-primary: #6c63ff;
  }
}

App Store Deployment

# Build web assets
ionic build --prod

# Sync to native projects
npx cap sync

# Android: Open Android Studio → Build → Generate Signed APK/Bundle
npx cap open android

# iOS: Open Xcode → Product → Archive → Distribute
npx cap open ios

Key Takeaways

  • Ionic uses web technologies inside a native WebView — share 90%+ code with your web app
  • Capacitor bridges to native APIs (camera, GPS, push, filesystem, biometrics)
  • Use ionic cap run with --livereload for fast development on real devices
  • Ionic components automatically adapt to iOS/Android styling (Material on Android, Cupertino on iOS)
  • Use Platform.is() for platform-specific behavior
  • Store sensitive data with @capacitor/preferences (encrypted on native)
  • For performance-critical rendering (games, AR), consider native frameworks instead