一键导入
router-first-methodology
Doguhan Uluca's Router-First Architecture - The 7 steps for designing scalable Angular applications by defining routes before components
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Doguhan Uluca's Router-First Architecture - The 7 steps for designing scalable Angular applications by defining routes before components
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Proven architectural patterns for building scalable Angular applications in enterprise environments with large teams
Essential RxJS operators and patterns for Angular development - transformation, filtering, combination, and error handling operators
Angular Signals - Modern reactive state management patterns with computed signals, effects, and interoperability with RxJS
Comprehensive guide to Angular bundle optimization, code splitting, tree shaking, and lazy loading strategies
Deep dive into Angular change detection optimization, OnPush strategy, Signals, and Zone.js patterns
Secure authentication and authorization patterns including JWT, OAuth2, guards, and role-based access control
| name | router-first-methodology |
| description | Doguhan Uluca's Router-First Architecture - The 7 steps for designing scalable Angular applications by defining routes before components |
Author: Doguhan Uluca
Source: Angular for Enterprise Applications, 3rd Edition
Context: Enterprise Angular architecture for teams of 5-100+ developers
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.
Traditional development often starts with components, leading to:
Router-First solves this by:
Goal: Define what features your application needs
Process:
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
Goal: Plan bundle structure for optimal performance
Process:
Example:
// 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:
// ❌ BAD: Everything imported at root
import { DashboardModule } from './dashboard';
import { ProductsModule } from './products';
import { OrdersModule } from './orders';
Best Practice:
// ✅ GOOD: Lazy loaded via routes
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.routes')
}
Goal: Create navigable shell with placeholder content
Process:
Example:
// 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' }
}
];
// 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
Goal: Components receive data, don't manage global state
Process:
Example:
// ❌ BAD: Component manages state
@Component({...})
export class ProductListComponent {
products: Product[] = [];
constructor(private http: HttpClient) {
this.http.get('/api/products').subscribe(data => {
this.products = data;
});
}
}
// ✅ 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();
}
Goal: Separate smart (container) and dumb (presentational) components
Smart Components:
Dumb Components:
Example:
// 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>();
}
Goal: Clear separation between reusable UI and feature-specific components
User Controls (Shared):
shared/components/Feature Components:
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/
Goal: DRY principle with TypeScript and ES features
Techniques:
// shared/utils/date.utils.ts
export function formatDate(date: Date): string {
return date.toLocaleDateString('en-US');
}
// core/models/api-response.interface.ts
export interface ApiResponse<T> {
data: T;
message: string;
status: number;
}
// core/base/base-component.ts
export abstract class BaseComponent implements OnDestroy {
protected destroy$ = new Subject<void>();
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
// shared/mixins/timestamp.mixin.ts
export function WithTimestamp<T extends Constructor>(Base: T) {
return class extends Base {
createdAt = new Date();
updatedAt = new Date();
};
}
Team: 15 developers
Timeline: 6 months
Features: 12 major features
Router-First Implementation:
Week 1: Route planning
Week 2-3: Core setup
Week 4-24: Parallel development
Result:
// ❌ 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
// ❌ BAD: Direct component dependencies
export class DashboardComponent {
constructor(private productList: ProductListComponent) {}
}
// ✅ GOOD: Service-based communication
export class DashboardComponent {
constructor(private productService: ProductService) {}
}
// ❌ BAD: Eager loading everything
imports: [
DashboardModule,
ProductsModule,
OrdersModule
]
// ✅ GOOD: Lazy load features
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.routes')
}
Before claiming Router-First compliance:
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.