Skip to main content

Overview

Rodando Passenger uses NgRx Signals for state management, providing a reactive and type-safe approach to managing application state. Stores are simple, signal-based containers that hold state and expose computed selectors.

Store Pattern

Each store follows a consistent pattern:
  1. State Interface: Defines the shape of the state
  2. Signal-based State: Uses Angular signals for reactivity
  3. Computed Selectors: Derived state via computed()
  4. Mutation Methods: Methods to update state
  5. Injectable Service: Provided at root level

Available Stores

AuthStore

Authentication state and token management

TripPlannerStore

Trip planning and route calculation state

SessionsStore

Session lifecycle and device tracking

UsersStore

User profiles and passenger information

AuthStore

Manages authentication state including tokens, user profile, and session information.

State Interface

src/app/store/auth/auth.store.ts
export interface AuthState {
  accessToken: string | null;
  refreshTokenInMemory: string | null;
  user: UserProfile | null;
  loading: boolean;
  error: any | null;
  sessionType?: SessionType | null;
  usesCookie?: boolean | null;
  accessTokenExpiresAt?: number | null;
  refreshTokenExpiresAt?: number | null;
  sid?: string | null;
  refreshInProgress?: boolean;
}

Computed Selectors

readonly accessToken = computed(() => this._state().accessToken);
readonly user = computed(() => this._state().user);
readonly loading = computed(() => this._state().loading);
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();
});
readonly accessTokenExpiresIn = computed(() => {
  const at = this._state().accessTokenExpiresAt;
  if (!at) return null;
  return Math.max(0, at - Date.now());
});

Mutation Methods

setAccessToken
(token: string | null) => void
Updates the access token in state
setUser
(user: UserProfile | null) => void
Updates the user profile
setAuth
(payload: Partial<AuthState>) => void
Atomically updates multiple auth fields
clear
() => void
Resets state to initial values

Usage Example

import { inject } from '@angular/core';
import { AuthStore } from './store/auth/auth.store';

export class MyComponent {
  private authStore = inject(AuthStore);

  // Access computed signals
  isAuthenticated = this.authStore.isAuthenticated();
  user = this.authStore.user();
  expiresIn = this.authStore.accessTokenExpiresIn();

  // Update state
  logout() {
    this.authStore.clear();
  }
}

TripPlannerStore

Manages trip planning state including origin, destination, route calculations, and fare estimates.

State Interface

src/app/store/trips/trip-planner.store.ts
export interface TripPlannerState {
  originPoint: LatLng | null;
  originText: string | null;
  destinationPoint: LatLng | null;
  destinationText: string | null;
  routeSummary: RouteSummary | null;
  selectedVehicleCategoryId: string | null;
  selectedServiceClassId: string | null;
  fareQuote: FareQuote | null;
  loading: boolean;
  error: any | null;
}

Key Selectors

readonly originPoint = computed(() => this._state().originPoint);
readonly destinationPoint = computed(() => this._state().destinationPoint);
readonly routeSummary = computed(() => this._state().routeSummary);
readonly fareQuote = computed(() => this._state().fareQuote);
readonly isReadyToRequest = computed(() => {
  const s = this._state();
  return !!(s.originPoint && s.destinationPoint && s.routeSummary && s.fareQuote);
});

Mutation Methods

setOrigin
(point: LatLng, text?: string) => void
Sets the trip origin location
setDestination
(point: LatLng, text?: string) => void
Sets the trip destination location
setRoute
(route: RouteSummary) => void
Updates the calculated route
setFareQuote
(quote: FareQuote) => void
Sets the fare estimate
reset
() => void
Clears all trip planning state

SessionsStore

Tracks active sessions and device information.

State Interface

export interface SessionsState {
  sessions: Session[];
  currentSession: Session | null;
  loading: boolean;
  error: any | null;
}

UsersStore

Manages user profiles and passenger-specific data.

State Interface

export interface UsersState {
  currentUser: User | null;
  passengers: Record<string, Passenger>;
  loading: boolean;
  error: any | null;
}

Best Practices

Signal-Based Reactivity: Stores use Angular signals which automatically track dependencies and trigger updates when state changes.
Computed Selectors: Use computed selectors to derive state. They are memoized and only recalculate when dependencies change.
Direct State Access: Never mutate _state directly. Always use provided mutation methods to ensure proper signal updates.

Store Communication

Stores are independent but can be composed:
export class MyFacade {
  private authStore = inject(AuthStore);
  private tripStore = inject(TripPlannerStore);

  // Combine signals from multiple stores
  canRequestTrip = computed(() => {
    return this.authStore.isAuthenticated() && 
           this.tripStore.isReadyToRequest();
  });
}

Facades

Business logic layer that orchestrates stores and services

Effects

Side effect management with NgRx Effects

State Management

Overall state management architecture

Build docs developers (and LLMs) love