# Router-First Methodology
**Author:** Doguhan Uluca
**Source:** Angular for Enterprise Applications, 3rd Edition
**Context:** Enterprise Angular architecture for teams of 5-100+ developers
---
## Core Concept
**Router-First Architecture** is a methodology that enforces designing your application's routing structure BEFORE implementing components. This approach ensures high-level thinking, team consensus, and scalable architecture from day one.
---
## Why Router-First?
Traditional development often starts with components, leading to:
- ❌ Unclear application structure
- ❌ Tight coupling between features
- ❌ Difficult to refactor later
- ❌ Hard to parallelize team work
- ❌ Performance issues at scale
Router-First solves this by:
- ✅ Forcing architectural decisions early
- ✅ Creating clear feature boundaries
- ✅ Enabling lazy loading from the start
- ✅ Facilitating team collaboration
- ✅ Making the app structure visible in code
---
## The 7 Steps
### Step 1: Develop a Roadmap and Scope
**Goal:** Define what features your application needs
**Process:**
1. List all user-facing features
2. Identify MVP vs. future features
3. Group related functionality
4. Define user roles and permissions
**Example:**
```
E-commerce App Roadmap:
Phase 1 (MVP):
- Product browsing
- Shopping cart
- Checkout
- User authentication
Phase 2:
- Order history
- Product reviews
- Wishlist
- Admin panel
Phase 3:
- Analytics dashboard
- Inventory management
- Customer support
```
**Output:** Feature list with priorities
---
### Step 2: Design with Lazy Loading in Mind
**Goal:** Plan bundle structure for optimal performance
**Process:**
1. Each major feature = separate lazy-loaded module
2. Identify shared dependencies
3. Plan loading strategies
4. Set bundle size budgets
**Example:**
```typescript
// Bundle planning
Initial Load (Critical Path):
- Authentication (50 KB)
- Layout shell (30 KB)
- Core services (40 KB)
Total: 120 KB ✅
Lazy Loaded Features:
- Dashboard (60 KB)
- Products (80 KB)
- Orders (45 KB)
- Admin (120 KB)
Strategy:
- Preload Dashboard after login
- Lazy load others on-demand
- Code split large features
```
**Anti-pattern:**
```typescript
// ❌ BAD: Everything imported at root
import { DashboardModule } from './dashboard';
import { ProductsModule } from './products';
import { OrdersModule } from './orders';
```
**Best Practice:**
```typescript
// ✅ GOOD: Lazy loaded via routes
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.routes')
}
```
---
### Step 3: Implement Walking-Skeleton Navigation
**Goal:** Create navigable shell with placeholder content
**Process:**
1. Define all routes in app.routes.ts
2. Create shell components (empty templates)
3. Verify navigation works
4. Add breadcrumbs and titles
**Example:**
```typescript
// app.routes.ts - Walking skeleton
export const routes: Routes = [
{
path: '',
redirectTo: '/dashboard',
pathMatch: 'full'
},
{
path: 'dashboard',
loadComponent: () => import('./features/dashboard/dashboard.component')
.then(m => m.DashboardComponent),
data: { breadcrumb: 'Dashboard' }
},
{
path: 'products',
loadComponent: () => import('./features/products/products.component')
.then(m => m.ProductsComponent),
data: { breadcrumb: 'Products' }
},
{
path: 'orders',
loadComponent: () => import('./features/orders/orders.component')
.then(m => m.OrdersComponent),
data: { breadcrumb: 'Orders' }
}
];
```
```typescript
// dashboard.component.ts - Shell component
@Component({
selector: 'app-dashboard',
standalone: true,
template: `
Dashboard
Coming soon...
`
})
export class DashboardComponent {}
```
**Benefit:** Team can navigate the app before any features are implemented
---
### Step 4: Achieve Stateless, Data-Driven Design
**Goal:** Components receive data, don't manage global state
**Process:**
1. Services handle state and HTTP
2. Components receive data via inputs/signals
3. Components emit events, not side effects
4. Use observables for async data
**Example:**
```typescript
// ❌ BAD: Component manages state
@Component({...})
export class ProductListComponent {
products: Product[] = [];
constructor(private http: HttpClient) {
this.http.get('/api/products').subscribe(data => {
this.products = data;
});
}
}
```
```typescript
// ✅ GOOD: Service manages state
@Injectable({ providedIn: 'root' })
export class ProductService {
private products$ = new BehaviorSubject([]);
getProducts(): Observable {
return this.http.get('/api/products').pipe(
tap(products => this.products$.next(products))
);
}
}
@Component({...})
export class ProductListComponent {
products$ = inject(ProductService).getProducts();
}
```
---
### Step 5: Enforce Decoupled Component Architecture
**Goal:** Separate smart (container) and dumb (presentational) components
**Smart Components:**
- Manage data fetching
- Handle business logic
- Communicate with services
- Located in feature folders
**Dumb Components:**
- Receive data via @Input
- Emit events via @Output
- No business logic
- Located in shared folder
**Example:**
```typescript
// Smart component (container)
@Component({
selector: 'app-product-list',
template: `
@for (product of products(); track product.id) {
}
`
})
export class ProductListComponent {
private productService = inject(ProductService);
products = toSignal(this.productService.getProducts());
handleAddToCart(productId: string) {
this.cartService.addItem(productId);
}
}
// Dumb component (presentational)
@Component({
selector: 'app-product-card',
template: `
{{ product.name }}
{{ product.price | currency }}
`
})
export class ProductCardComponent {
@Input({ required: true }) product!: Product;
@Output() addToCart = new EventEmitter();
}
```
---
### Step 6: Differentiate User Controls vs Components
**Goal:** Clear separation between reusable UI and feature-specific components
**User Controls (Shared):**
- Generic UI elements
- No business logic
- Highly reusable
- Location: `shared/components/`
**Feature Components:**
- Feature-specific logic
- Use shared controls
- Business logic included
- Location: `features//components/`
**Example Structure:**
```
shared/components/ # User Controls
├── button/
├── input/
├── card/
├── modal/
└── data-table/
features/products/ # Feature Components
├── product-list/
├── product-detail/
├── product-form/
└── product-search/
```
---
### Step 7: Maximize Code Reuse
**Goal:** DRY principle with TypeScript and ES features
**Techniques:**
1. **Shared Utilities**
```typescript
// shared/utils/date.utils.ts
export function formatDate(date: Date): string {
return date.toLocaleDateString('en-US');
}
```
2. **Shared Interfaces**
```typescript
// core/models/api-response.interface.ts
export interface ApiResponse {
data: T;
message: string;
status: number;
}
```
3. **Base Classes (use sparingly)**
```typescript
// core/base/base-component.ts
export abstract class BaseComponent implements OnDestroy {
protected destroy$ = new Subject();
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
```
4. **Mixins**
```typescript
// shared/mixins/timestamp.mixin.ts
export function WithTimestamp(Base: T) {
return class extends Base {
createdAt = new Date();
updatedAt = new Date();
};
}
```
---
## Real-World Application
### Case Study: E-commerce Platform
**Team:** 15 developers
**Timeline:** 6 months
**Features:** 12 major features
**Router-First Implementation:**
1. **Week 1:** Route planning
- Defined all 12 features as routes
- Created walking skeleton
- Team reviewed and agreed on structure
2. **Week 2-3:** Core setup
- Implemented auth guards
- Set up core services
- Created shared components
3. **Week 4-24:** Parallel development
- 3 teams worked on different features simultaneously
- No merge conflicts (clear boundaries)
- Easy to track progress (routes visible)
4. **Result:**
- On-time delivery
- 185 KB initial bundle
- 45 KB average feature bundle
- Easy onboarding for new devs
---
## Common Mistakes
### 1. Starting with Components
```typescript
// ❌ WRONG ORDER
1. Build dashboard component
2. Build product list component
3. Figure out routing later
// ✅ CORRECT ORDER
1. Design routes
2. Create shell components
3. Implement features
```
### 2. Tight Coupling
```typescript
// ❌ BAD: Direct component dependencies
export class DashboardComponent {
constructor(private productList: ProductListComponent) {}
}
// ✅ GOOD: Service-based communication
export class DashboardComponent {
constructor(private productService: ProductService) {}
}
```
### 3. Ignoring Lazy Loading
```typescript
// ❌ BAD: Eager loading everything
imports: [
DashboardModule,
ProductsModule,
OrdersModule
]
// ✅ GOOD: Lazy load features
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.routes')
}
```
---
## Checklist
Before claiming Router-First compliance:
- [ ] Routes defined before component implementation
- [ ] All features lazy loaded (except critical path)
- [ ] Walking skeleton navigation works
- [ ] Smart/Dumb component separation
- [ ] Services manage state, not components
- [ ] Shared components in shared folder
- [ ] Feature components in feature folders
- [ ] Clear team agreement on structure
- [ ] Bundle size budgets defined
- [ ] Documentation of routing decisions
---
## Summary
Router-First Architecture is about **planning before building**. By designing routes first, you create a scalable, maintainable, and performant Angular application that grows with your team.
**Key Takeaway:** If you can see your entire application structure by looking at app.routes.ts, you're doing it right.