Skip to main content

Layout Components

Rodando Passenger uses layout components to provide consistent UI structure across different sections of the app. The two primary layouts are DefaultLayoutComponent (tabs + sidebar) and MapLayoutComponent (fullscreen map).

Layout Architecture

Layout components act as wrappers for route content, providing shared UI elements:
Layout-based routing means routes are grouped by their visual structure. Routes under DefaultLayoutComponent automatically inherit the tab bar and sidebar, while other routes render fullscreen.

DefaultLayoutComponent

Overview

The DefaultLayoutComponent provides the main app shell with:
  • Header (optional app bar)
  • Content area (rendered via <ion-router-outlet>)
  • Tab bar (bottom navigation)
  • Sidebar (hamburger menu)

Implementation

src/app/shared/layouts/default-layout/default-layout.component.ts
import { Component, OnInit } from '@angular/core';
import { 
  IonHeader, 
  IonRouterOutlet, 
  IonFooter, 
  IonToolbar, 
  IonButton, 
  IonIcon, 
  IonContent 
} from "@ionic/angular/standalone";
import { FloatingButtonComponent } from "@/app/components/floating-button/floating-button.component";
import { RouterModule } from '@angular/router';

@Component({
  selector: 'app-default-layout',
  templateUrl: './default-layout.component.html',
  styleUrls: ['./default-layout.component.scss'],
  standalone: true,
  imports: [
    IonHeader, 
    FloatingButtonComponent, 
    IonRouterOutlet, 
    IonFooter, 
    IonToolbar, 
    IonButton, 
    IonIcon, 
    RouterModule, 
    IonContent
  ],
})
export class DefaultLayoutComponent implements OnInit {
  constructor() {}

  ngOnInit() {}
}

Template Structure

The template typically includes:
default-layout.component.html (typical structure)
<ion-header>
  <ion-toolbar>
    <!-- App title, menu button, notifications -->
  </ion-toolbar>
</ion-header>

<ion-content>
  <!-- Router outlet renders child routes -->
  <ion-router-outlet></ion-router-outlet>
</ion-content>

<ion-footer>
  <ion-toolbar>
    <!-- Tab bar with navigation buttons -->
    <ion-button routerLink="/home">
      <ion-icon name="home"></ion-icon>
    </ion-button>
    <ion-button routerLink="/trips/active">
      <ion-icon name="car"></ion-icon>
    </ion-button>
    <ion-button routerLink="/profile">
      <ion-icon name="person"></ion-icon>
    </ion-button>
  </ion-toolbar>
</ion-footer>

<!-- Sidebar menu -->
<ion-menu side="start" contentId="main-content">
  <ion-header>
    <ion-toolbar>
      <ion-title>Menu</ion-title>
    </ion-toolbar>
  </ion-header>
  <ion-content>
    <ion-list>
      <ion-item routerLink="/wallet">Wallet</ion-item>
      <ion-item routerLink="/settings">Settings</ion-item>
      <ion-item routerLink="/support">Support</ion-item>
    </ion-list>
  </ion-content>
</ion-menu>
The <ion-router-outlet> is where child routes render. It’s similar to Angular’s <router-outlet> but optimized for Ionic’s animations and navigation stack.

Routes Using Default Layout

All routes under the root path use this layout:
src/app/app.routes.ts:22-125 (excerpt)
{
  path: '',
  component: DefaultLayoutComponent, // ← Layout wrapper
  canActivateChild: [authGuardWithRefreshChild],
  children: [
    // Tab pages
    {
      path: 'home',
      loadComponent: () => import('./features/tabs/home/home.component'),
      data: { title: 'Inicio', tab: 'home' },
    },
    {
      path: 'trips',
      loadComponent: () => import('./features/tabs/trips/trips.component'),
      children: [
        {
          path: 'active',
          loadComponent: () => import('./features/tabs/trips/active/active.component'),
          data: { title: 'Viaje Activo', tab: 'trips' },
        },
        {
          path: 'history',
          loadComponent: () => import('./features/tabs/trips/historial/historial.component'),
          data: { title: 'Historial', tab: 'trips' },
        },
      ],
    },
    {
      path: 'profile',
      loadComponent: () => import('./features/tabs/profile/profile.component'),
      data: { title: 'Perfil', tab: 'profile' },
    },

    // Sidebar pages
    {
      path: 'wallet',
      loadComponent: () => import('./features/sidebar/wallet/wallet.component'),
      data: { title: 'Wallet' },
    },
    {
      path: 'settings',
      loadComponent: () => import('./features/sidebar/settings/settings.component'),
      data: { title: 'Configuración' },
    },
    // ... more sidebar routes
  ],
}

Layout Features

Bottom navigation for primary features:
  • Home (dashboard)
  • Trips (active/history)
  • Profile (user info)
Uses routerLink to navigate between tabs. Active tab is highlighted using route data.

MapLayoutComponent

Overview

The MapLayoutComponent provides a fullscreen map view without tabs or sidebar:
src/app/shared/layouts/map-layout/map-layout.component.ts
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-map-layout',
  templateUrl: './map-layout.component.html',
  styleUrls: ['./map-layout.component.scss'],
})
export class MapLayoutComponent implements OnInit {
  constructor() {}

  ngOnInit() {}
}

Template Structure

map-layout.component.html (typical structure)
<div class="map-container">
  <!-- Fullscreen map (Mapbox, Google Maps, etc.) -->
  <div #mapElement class="map"></div>
  
  <!-- Overlays -->
  <div class="map-controls">
    <button class="back-button" (click)="goBack()">
      <ion-icon name="arrow-back"></ion-icon>
    </button>
    <button class="locate-me" (click)="centerOnUser()">
      <ion-icon name="locate"></ion-icon>
    </button>
  </div>

  <!-- Bottom sheet for trip details -->
  <div class="trip-details-sheet">
    <h3>Trip Details</h3>
    <p>Distance: {{ distance }}</p>
    <p>Duration: {{ duration }}</p>
    <button (click)="confirmTrip()">Confirm Trip</button>
  </div>
</div>

Routes Using Map Layout

The /map route uses this layout for fullscreen map interaction:
src/app/app.routes.ts:128-132
{
  path: 'map',
  loadComponent: () => import('./features/tabs/map/map.component'),
  canActivate: [authGuardWithRefresh],
}
The map route is outside the DefaultLayoutComponent parent, so it renders fullscreen without tabs or sidebar.

Layout Switching

Routing between layouts is seamless:
// From tab page to fullscreen map
this.router.navigate(['/map']);

// From map back to tabs
this.router.navigate(['/home']);

Conditional Layout Rendering

Components can detect their layout context:
import { ActivatedRoute, Router } from '@angular/router';

export class ExampleComponent {
  private router = inject(Router);

  isFullscreen(): boolean {
    // Check if current route is outside layout
    return this.router.url.includes('/map');
  }
}

Shared Layout Components

Floating Button

Reusable floating action button used in DefaultLayoutComponent:
import { FloatingButtonComponent } from '@/app/components/floating-button/floating-button.component';

@Component({
  // ...
  imports: [FloatingButtonComponent],
})

Header Components

Shared header elements (notifications, menu button):
<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
    </ion-buttons>
    <ion-title>{{ title }}</ion-title>
    <ion-buttons slot="end">
      <ion-button routerLink="/notifications">
        <ion-icon name="notifications"></ion-icon>
        <ion-badge *ngIf="unreadCount > 0">{{ unreadCount }}</ion-badge>
      </ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

Layout Component Structure

DefaultLayoutComponent
├── Header (IonHeader)
│   ├── Menu Button
│   ├── Title
│   └── Notifications
├── Content (IonContent)
│   └── Router Outlet (child routes render here)
├── Footer (IonFooter)
│   └── Tab Bar (IonTabs)
│       ├── Home Tab
│       ├── Trips Tab
│       └── Profile Tab
└── Sidebar (IonMenu)
    ├── Profile Section
    ├── Wallet
    ├── Settings
    └── Logout
MapLayoutComponent
├── Map Container (fullscreen)
│   ├── Mapbox GL JS Map
│   ├── Origin Marker
│   ├── Destination Marker
│   └── Route Polyline
├── Overlay Controls
│   ├── Back Button
│   ├── Center on User
│   └── Layers Toggle
└── Bottom Sheet
    ├── Trip Details
    ├── Fare Estimate
    └── Confirm Button

Layout State Management

Dynamic Tab Highlighting

Layouts can read route data to highlight active tabs:
export class DefaultLayoutComponent implements OnInit {
  private route = inject(ActivatedRoute);
  activeTab: string = 'home';

  ngOnInit() {
    // Subscribe to route changes
    this.route.data.subscribe(data => {
      this.activeTab = data['tab'] || 'home';
    });
  }
}
<ion-tab-button [class.active]="activeTab === 'home'" routerLink="/home">
  <ion-icon name="home"></ion-icon>
  <ion-label>Home</ion-label>
</ion-tab-button>
Layouts manage sidebar open/close state:
import { MenuController } from '@ionic/angular/standalone';

export class DefaultLayoutComponent {
  private menuCtrl = inject(MenuController);

  openMenu() {
    this.menuCtrl.open();
  }

  closeMenu() {
    this.menuCtrl.close();
  }
}

Responsive Layouts

Desktop vs Mobile

Layouts can adapt to screen size:
// Default: mobile (tabs at bottom)
.tabs {
  position: fixed;
  bottom: 0;
  width: 100%;
}

// Desktop: sidebar navigation
@media (min-width: 768px) {
  .tabs {
    display: none; // Hide tabs
  }
  .sidebar {
    display: block; // Show sidebar
    position: fixed;
    left: 0;
    width: 250px;
  }
}

Best Practices

Keep Layouts Thin

Layouts should only handle structure. Business logic belongs in pages/components.

Use Router Outlets

Always use <ion-router-outlet> in Ionic apps for proper navigation animations.

Share Common Elements

Extract reusable header/footer components used across layouts.

Handle Back Navigation

Fullscreen layouts should provide a way to return to the main app flow.

Common Layout Patterns

Pattern 1: Tabs + Sidebar

Used for main app navigation:
  • Tabs: Primary features (3-5 items)
  • Sidebar: Secondary features (settings, account, etc.)
  • Example: DefaultLayoutComponent

Pattern 2: Fullscreen

Used for immersive experiences:
  • No tabs or sidebar
  • Back button to return
  • Example: MapLayoutComponent, Auth pages

Pattern 3: Modal/Overlay

Used for temporary actions:
  • Overlays current view
  • Dismiss to return
  • Example: Trip details sheet, rating modal

Next Steps

Routing

Learn how routes map to layouts

Components

Explore reusable UI components

Build docs developers (and LLMs) love