14

Module 14: Routing Between Angular Micro Frontends

Chapter 14 • Advanced

30 min

Routing Between Angular Micro Frontends

Users navigate normally:

  • /products
  • /checkout

Behind the scenes:

  • Routes load different applications
  • Everything feels like a single SPA

Routing Architecture

Host Routing

typescript.js
const routes: Routes = [
  {
    path: 'products',
    loadChildren: () => import('products/ProductsModule')
      .then(m => m.ProductsModule)
  },
  {
    path: 'checkout',
    loadChildren: () => import('checkout/CheckoutModule')
      .then(m => m.CheckoutModule)
  },
  {
    path: '',
    redirectTo: '/products',
    pathMatch: 'full'
  }
]

How It Works

  1. User navigates to /products
  2. Host detects route
  3. Host loads Products remote module
  4. Products module handles its own routes
  5. User sees Products app

Remote Routing

Products Remote Routes

typescript.js
// products-app/src/app/products/products-routing.module.ts
const routes: Routes = [
  { path: '', component: ProductsListComponent },
  { path: ':id', component: ProductDetailComponent }
]

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ProductsRoutingModule {}

Checkout Remote Routes

typescript.js
// checkout-app/src/app/checkout/checkout-routing.module.ts
const routes: Routes = [
  { path: '', component: CartComponent },
  { path: 'payment', component: PaymentComponent },
  { path: 'confirmation', component: ConfirmationComponent }
]

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class CheckoutRoutingModule {}

Route Configuration

Host App Routing Module

typescript.js
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'

const routes: Routes = [
  {
    path: 'products',
    loadChildren: () => import('products/ProductsModule')
      .then(m => m.ProductsModule)
  },
  {
    path: 'checkout',
    loadChildren: () => import('checkout/CheckoutModule')
      .then(m => m.CheckoutModule)
  }
]

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

Navigation Patterns

Programmatic Navigation

typescript.js
import { Router } from '@angular/router'

export class ProductsComponent {
  constructor(private router: Router) {}

  goToCheckout() {
    this.router.navigate(['/checkout'])
  }

  goToProduct(id: string) {
    this.router.navigate(['/products', id])
  }
}

Template Navigation

html.js
<a routerLink="/products">Products</a>
<a routerLink="/checkout">Checkout</a>
<a [routerLink]="['/products', productId]">View Product</a>

Cross-App Navigation

From Products to Checkout

typescript.js
// In Products app
export class ProductDetailComponent {
  constructor(private router: Router) {}

  addToCart() {
    // Navigate to checkout (different app)
    this.router.navigate(['/checkout'])
  }
}

How it works:

  • Products app uses shared Router
  • Router is provided by Host
  • Navigation works across apps

Route Guards

Auth Guard (Host Level)

typescript.js
import { Injectable } from '@angular/core'
import { CanActivate, Router } from '@angular/router'

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(private router: Router) {}

  canActivate(): boolean {
    if (this.isAuthenticated()) {
      return true
    }
    this.router.navigate(['/login'])
    return false
  }

  private isAuthenticated(): boolean {
    // Check authentication
    return true
  }
}

Use in Routes

typescript.js
const routes: Routes = [
  {
    path: 'checkout',
    loadChildren: () => import('checkout/CheckoutModule')
      .then(m => m.CheckoutModule),
    canActivate: [AuthGuard]
  }
]

Route Data Sharing

Passing Data via Route

typescript.js
// Navigate with state
this.router.navigate(['/checkout'], {
  state: { cartItems: this.cartItems }
})

// Read state
const navigation = this.router.getCurrentNavigation()
const state = navigation?.extras.state

Query Parameters

typescript.js
// Navigate with query params
this.router.navigate(['/products'], {
  queryParams: { category: 'electronics' }
})

// Read query params
this.route.queryParams.subscribe(params => {
  const category = params['category']
})

Deep Linking

Support Deep Links

typescript.js
// Direct URL: /products/123
// Works automatically with Angular routing

const routes: Routes = [
  {
    path: 'products',
    loadChildren: () => import('products/ProductsModule')
      .then(m => m.ProductsModule)
  }
]

// Products module handles: /products/:id

Route Preloading

Preload Strategy

typescript.js
import { PreloadAllModules } from '@angular/router'

RouterModule.forRoot(routes, {
  preloadingStrategy: PreloadAllModules
})

Benefits:

  • ✅ Faster navigation
  • ✅ Better UX
  • ✅ Background loading

Error Handling

Handle Route Errors

typescript.js
{
  path: 'products',
  loadChildren: () => import('products/ProductsModule')
    .then(m => m.ProductsModule)
    .catch(err => {
      console.error('Failed to load products module', err)
      // Fallback route
      return import('./fallback/fallback.module')
        .then(m => m.FallbackModule)
    })
}

Standalone Routing

Remote Apps Work Standalone

typescript.js
// products-app can run standalone
const routes: Routes = [
  {
    path: 'products',
    loadChildren: () => import('./products/products.module')
      .then(m => m.ProductsModule)
  }
]

Benefits:

  • ✅ Development without Host
  • ✅ Testing in isolation
  • ✅ Independent deployment

Best Practices

✅ Do

  • Use consistent route structure
  • Handle route errors
  • Support deep linking
  • Use route guards
  • Preload critical routes

❌ Don't

  • Don't hardcode routes
  • Don't ignore errors
  • Don't break deep links
  • Don't skip route guards

Next Steps

Routing is configured!

Next module: Deployment strategies.