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. Action button (e.g., “Request Trip”):
Positioned over content
Visibility controlled by route
Imported via FloatingButtonComponent
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
Navigation Between Layouts
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
Reusable floating action button used in DefaultLayoutComponent:
import { FloatingButtonComponent } from '@/app/components/floating-button/floating-button.component' ;
@ Component ({
// ...
imports: [ FloatingButtonComponent ],
})
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 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 Structure
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 : 768 px ) {
.tabs {
display : none ; // Hide tabs
}
.sidebar {
display : block ; // Show sidebar
position : fixed ;
left : 0 ;
width : 250 px ;
}
}
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
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