router-first-methodology
Doguhan Uluca's Router-First Architecture - The 7 steps for designing scalable Angular applications by defining routes before components
What this skill does
# 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: `
<h1>Dashboard</h1>
<p>Coming soon...</p>
`
})
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<Product[]>([]);
getProducts(): Observable<Product[]> {
return this.http.get<Product[]>('/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) {
<app-product-card
[product]="product"
(addToCart)="handleAddToCart($event)"
/>
}
`
})
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: `
<div class="card">
<h3>{{ product.name }}</h3>
<p>{{ product.price | currency }}</p>
<button (click)="addToCart.emit(product.id)">
Add to Cart
</button>
</div>
`
})
export class ProductCardComponent {
@Input({ required: true }) product!: Product;
@Output() addToCart = new EventEmitter<string>();
}
```
---
### 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/<feature>/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<T> {
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<void>();
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
```
4. **Mixins**
```typescript
// shared/mixins/timestamp.mixin.ts
export function WithTimestamp<T extends Constructor>(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 ImplementatiRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.