Skip to main content

Architecture Overview

Rodando Passenger is an Ionic Angular mobile application built with modern standalone components, reactive state management using NgRx Signals, and a feature-based modular architecture.

Technology Stack

Framework

Angular 18+ with standalone components

UI Framework

Ionic 8+ for cross-platform mobile UI

State Management

NgRx Signals (Store + Facade pattern)

Platform

iOS & Android via Capacitor

Project Structure

The application follows a feature-based architecture organized into clear, modular directories:
src/app/
├── core/              # Singleton services, guards, interceptors
│   ├── guards/        # Route guards (auth, guest)
│   ├── services/      # HTTP clients, utilities
│   ├── models/        # TypeScript interfaces/types
│   └── providers/     # Platform providers (location, etc.)

├── features/          # Feature modules (lazy-loaded)
│   ├── auth/          # Authentication (login, signup)
│   ├── tabs/          # Main tab pages (home, trips, profile)
│   ├── sidebar/       # Sidebar pages (settings, wallet, etc.)
│   └── passenger/     # Passenger-specific features

├── shared/            # Shared components and layouts
│   ├── layouts/       # Layout components (default, map)
│   └── components/    # Reusable UI components

├── store/             # NgRx Signals state management
│   ├── auth/          # Authentication state
│   ├── users/         # User profile state
│   ├── trips/         # Trip planning & lifecycle
│   └── sessions/      # Session management

├── components/        # Global components
├── pages/             # Standalone pages
└── app.routes.ts      # Root routing configuration
Standalone Components: The app uses Angular’s standalone component architecture, eliminating the need for NgModules and enabling more granular code splitting.

Architectural Patterns

1. Store + Facade Pattern

The application implements a Store + Facade pattern for state management:
Store holds the reactive state using Angular signals:
  • Defines state interface and initial state
  • Provides computed selectors
  • Exposes mutation methods
  • Location: src/app/store/*/ (e.g., auth.store.ts)

2. Dependency Injection

All services and stores use Angular’s providedIn: 'root' pattern for singleton instances:
src/app/store/auth/auth.store.ts
@Injectable({ providedIn: 'root' })
export class AuthStore {
  private readonly _state = signal<AuthState>({ ...initialState });
  
  readonly accessToken = computed(() => this._state().accessToken);
  readonly user = computed(() => this._state().user);
  readonly isAuthenticated = computed(() => {
    const token = this._state().accessToken;
    const user = this._state().user;
    const exp = this._state().accessTokenExpiresAt ?? 0;
    return !!token && !!user && exp > Date.now();
  });
}

3. Lazy Loading

All feature modules are lazy-loaded using dynamic imports:
src/app/app.routes.ts (excerpt)
{
  path: 'auth',
  loadComponent: () => import('./features/auth/auth.component'),
  canActivate: [guestGuard],
  children: [
    {
      path: '',
      loadChildren: () => import('./features/auth/auth.routes'),
    },
  ],
}

Core Services Architecture

The core/ directory contains singleton services organized by responsibility:
  • AuthService - Authentication endpoints (/auth/login, /auth/refresh)
  • TripsApiService - Trip management endpoints
  • MapboxPlacesService - Geocoding and place search
  • SecureStorageService - Encrypted storage for sensitive data
All HTTP services use Angular’s HttpClient and return RxJS observables.
  • authGuardWithRefresh - Protects private routes, attempts token refresh
  • authGuardWithRefreshChild - Protects child routes
  • guestGuard - Blocks authenticated users from auth pages
See Routing > Route Guards for implementation details.
  • LocationProvider - GPS/location tracking abstraction
  • Handles permissions, accuracy, and platform differences

Component Architecture

Standalone Components

All components are standalone and explicitly declare their dependencies:
Example Component
import { Component } from '@angular/core';
import { IonContent, IonHeader } from '@ionic/angular/standalone';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [CommonModule, IonContent, IonHeader],
  templateUrl: './example.component.html',
})
export class ExampleComponent {}

Component-Store Interaction

Components inject Facades (not stores directly) to maintain separation of concerns:
export class LoginComponent {
  private authFacade = inject(AuthFacade);

  async onLogin(credentials: LoginPayload) {
    this.authFacade.login(credentials)
      .pipe(take(1))
      .subscribe({
        next: (user) => this.router.navigate(['/home']),
        error: (err) => this.handleError(err)
      });
  }
}

Data Flow

  1. Component injects Facade
  2. Facade orchestrates Store + Services
  3. Service returns Observable data
  4. Facade updates Store state
  5. Store signals propagate to Component
This unidirectional data flow ensures predictable state updates and easier debugging.

Platform Considerations

Web vs Mobile Detection

The app detects platform to handle cookie-based (web) vs token-based (mobile) authentication:
src/app/store/auth/auth.facade.ts:41-48
getPlatformSync(): 'web' | 'mobile' {
  try {
    return this.platform.is('hybrid') ? 'mobile' : 'web';
  } catch {
    return 'web';
  }
}

Secure Storage

Sensitive data (refresh tokens) are stored using SecureStorageService:
  • Web: Uses encrypted localStorage
  • Mobile: Uses native secure keychain/keystore

Next Steps

State Management

Learn about NgRx Signals, Store/Facade patterns, and effects

Routing

Explore route configuration, guards, and lazy loading

Layouts

Understand layout components and navigation structure

API Integration

Connect to backend services and handle responses

Build docs developers (and LLMs) love