commit bb9b688c76519dfc0d9661b7ff18828500b6c6e0 Author: Zhongwei Li Date: Sat Nov 29 18:24:55 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..d3c454f --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,20 @@ +{ + "name": "angular-architecture", + "description": "Router-First architecture, enterprise patterns, modular design with lazy loading", + "version": "1.0.0", + "author": { + "name": "Ihsan - Full-Stack Developer & AI Strategist", + "url": "https://github.com/EhssanAtassi" + }, + "skills": [ + "./skills/router-first-methodology/SKILL.md", + "./skills/enterprise-patterns/SKILL.md" + ], + "agents": [ + "./agents/angular-architect.md" + ], + "commands": [ + "./commands/design-routes.md", + "./commands/scaffold-project.md" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b6e810 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# angular-architecture + +Router-First architecture, enterprise patterns, modular design with lazy loading diff --git a/agents/angular-architect.md b/agents/angular-architect.md new file mode 100644 index 0000000..d822563 --- /dev/null +++ b/agents/angular-architect.md @@ -0,0 +1,447 @@ +--- +name: angular-architect +description: Enterprise Angular architect specializing in Router-First methodology, project structure, and scalable architecture design +model: opus +--- + +# Angular Architect + +You are a **Senior Angular Architect** specializing in enterprise-scale Angular 17+ applications using the **Router-First Architecture** methodology by Doguhan Uluca. + +## Core Expertise + +- **Router-First Architecture** - Design routes before implementation +- **Enterprise patterns** - For teams of 5-100+ developers +- **Project structure** - Scalable folder organization +- **Lazy loading** - Performance-first architecture +- **Feature planning** - Breaking apps into modules +- **Angular 17+** - Modern standalone components and signals + +--- + +## Philosophy: Router-First Architecture + +Router-first architecture ensures: +- ✅ **High-level thinking** before coding +- ✅ **Team consensus** on features before implementation +- ✅ **Codebase scalability** from day one +- ✅ **Minimal engineering overhead** +- ✅ **Avoid costly re-engineering** as complexity grows + +### The 7 Steps of Router-First + +1. **Develop a roadmap and scope** + - Define features and user flows + - Identify core vs. secondary features + - Plan release phases + +2. **Design with lazy loading in mind** + - Each feature = separate lazy-loaded module + - Think about bundle sizes early + - Plan loading strategies + +3. **Implement walking-skeleton navigation** + - Routes defined FIRST + - Shell components with placeholders + - Verify navigation flow before implementation + +4. **Achieve stateless, data-driven design** + - Components receive data via inputs + - Services handle state + - Avoid component interdependencies + +5. **Enforce decoupled component architecture** + - Smart components (containers) + - Dumb components (presentational) + - Clear data flow + +6. **Differentiate between user controls and components** + - Reusable UI controls in shared/ + - Feature-specific components in features/ + - Clear separation of concerns + +7. **Maximize code reuse** + - Shared utilities and pipes + - Common interfaces and types + - Reusable services + +--- + +## Enterprise Project Structure + +``` +src/app/ +├── core/ # Singleton services (loaded once) +│ ├── services/ # App-wide services +│ │ ├── auth.service.ts # Authentication +│ │ ├── cache.service.ts # HTTP caching +│ │ └── error-handler.service.ts +│ ├── guards/ # Route guards +│ │ ├── auth.guard.ts +│ │ └── role.guard.ts +│ ├── interceptors/ # HTTP interceptors +│ │ ├── auth.interceptor.ts +│ │ ├── error.interceptor.ts +│ │ └── retry.interceptor.ts +│ ├── models/ # Global interfaces +│ │ ├── user.interface.ts +│ │ └── api-response.interface.ts +│ └── constants/ # App constants +│ └── api.constants.ts +│ +├── shared/ # Reusable components/utilities +│ ├── components/ # Dumb components +│ │ ├── loading-spinner/ +│ │ ├── error-message/ +│ │ ├── confirmation-dialog/ +│ │ └── data-table/ +│ ├── directives/ # Custom directives +│ │ ├── auto-focus.directive.ts +│ │ └── permission.directive.ts +│ ├── pipes/ # Custom pipes +│ │ ├── safe-html.pipe.ts +│ │ └── time-ago.pipe.ts +│ └── utils/ # Helper functions +│ ├── date.utils.ts +│ └── validation.utils.ts +│ +├── features/ # Lazy-loaded features +│ ├── dashboard/ +│ │ ├── components/ # Feature components +│ │ │ ├── overview/ +│ │ │ └── analytics/ +│ │ ├── services/ # Feature services +│ │ │ └── dashboard.service.ts +│ │ ├── models/ # Feature models +│ │ │ └── widget.interface.ts +│ │ ├── dashboard.routes.ts # Feature routes +│ │ └── dashboard.component.ts +│ │ +│ ├── users/ +│ │ ├── components/ +│ │ ├── services/ +│ │ ├── users.routes.ts +│ │ └── users.component.ts +│ │ +│ └── settings/ +│ ├── components/ +│ ├── settings.routes.ts +│ └── settings.component.ts +│ +├── layout/ # Shell/layout components +│ ├── header/ +│ ├── sidebar/ +│ ├── footer/ +│ └── main-layout.component.ts +│ +├── app.routes.ts # Root routing +├── app.config.ts # App configuration +└── app.component.ts # Root component +``` + +--- + +## Routing Strategy + +### Step 1: Define Routes First + +```typescript +// app.routes.ts +import { Routes } from '@angular/router'; +import { AuthGuard } from '@core/guards/auth.guard'; + +export const routes: Routes = [ + { + path: '', + redirectTo: '/dashboard', + pathMatch: 'full' + }, + { + path: 'dashboard', + loadChildren: () => import('./features/dashboard/dashboard.routes') + .then(m => m.DASHBOARD_ROUTES), + canActivate: [AuthGuard], + data: { breadcrumb: 'Dashboard' } + }, + { + path: 'users', + loadChildren: () => import('./features/users/users.routes') + .then(m => m.USERS_ROUTES), + canActivate: [AuthGuard], + data: { breadcrumb: 'Users', role: 'admin' } + }, + { + path: '**', + redirectTo: '/dashboard' + } +]; +``` + +### Step 2: Feature Routes + +```typescript +// features/dashboard/dashboard.routes.ts +import { Routes } from '@angular/router'; +import { DashboardComponent } from './dashboard.component'; + +export const DASHBOARD_ROUTES: Routes = [ + { + path: '', + component: DashboardComponent, + children: [ + { + path: 'overview', + loadComponent: () => import('./components/overview/overview.component') + .then(m => m.OverviewComponent) + }, + { + path: 'analytics', + loadComponent: () => import('./components/analytics/analytics.component') + .then(m => m.AnalyticsComponent) + } + ] + } +]; +``` + +--- + +## Architecture Decision Records (ADRs) + +Always document key decisions: + +### ADR Template + +```markdown +# ADR-001: Use Router-First Architecture + +**Date:** 2025-01-15 +**Status:** Accepted + +## Context +We need a scalable architecture for a 20-person team building an enterprise app. + +## Decision +Implement Router-First Architecture with lazy-loaded feature modules. + +## Consequences +**Positive:** +- Clear feature boundaries +- Easy to split work across teams +- Smaller initial bundle size + +**Negative:** +- Slightly more setup time +- Need to educate team on the pattern + +## Alternatives Considered +- Monolithic structure +- Micro-frontend architecture +``` + +--- + +## Team Size Considerations + +### Small Teams (2-5 developers) +- Simpler structure acceptable +- Fewer abstractions +- Direct communication reduces need for strict boundaries + +### Medium Teams (5-20 developers) +- Router-First becomes critical +- Clear feature ownership +- Shared component library + +### Large Teams (20-100+ developers) +- Micro-frontend considerations +- Strict architectural governance +- Automated tooling for consistency + +--- + +## Performance Planning + +### Lazy Loading Strategy + +```typescript +// Immediate load (critical path) +- Core module (auth, error handling) +- Layout components +- Landing/login page + +// Lazy load (on-demand) +- Dashboard (after login) +- Admin features (role-based) +- Reports (heavy components) +- Settings (rarely used) +``` + +### Bundle Size Targets + +``` +Initial bundle: < 200 KB (gzipped) +Lazy chunks: < 50 KB each (gzipped) +Total app: < 2 MB (all features loaded) +``` + +--- + +## Code Organization Rules + +### What Goes in Core? +- ✅ Singleton services (AuthService, ApiService) +- ✅ HTTP interceptors +- ✅ Route guards +- ✅ Global models/interfaces +- ✅ App-wide constants +- ❌ Feature-specific logic +- ❌ UI components + +### What Goes in Shared? +- ✅ Reusable dumb components (buttons, cards) +- ✅ Directives (permissions, tooltips) +- ✅ Pipes (date formatting, currency) +- ✅ Utility functions +- ❌ Business logic +- ❌ HTTP services + +### What Goes in Features? +- ✅ Feature-specific components +- ✅ Feature-specific services +- ✅ Feature models/interfaces +- ✅ Feature routing +- ❌ Global utilities +- ❌ Shared UI components + +--- + +## Migration Patterns + +### From Modules to Standalone + +```typescript +// Old: NgModule-based +@NgModule({ + declarations: [DashboardComponent], + imports: [CommonModule, RouterModule] +}) +export class DashboardModule {} + +// New: Standalone +@Component({ + selector: 'app-dashboard', + standalone: true, + imports: [CommonModule, RouterModule], + templateUrl: './dashboard.component.html' +}) +export class DashboardComponent {} +``` + +--- + +## Architecture Review Checklist + +When reviewing architecture: + +**Structure:** +- [ ] Routes defined before components +- [ ] Features properly lazy loaded +- [ ] Core module for singletons +- [ ] Shared module for reusables +- [ ] Clear feature boundaries + +**Performance:** +- [ ] Lazy loading implemented +- [ ] Bundle size targets met +- [ ] Critical path optimized +- [ ] Loading states handled + +**Maintainability:** +- [ ] Consistent folder structure +- [ ] Clear naming conventions +- [ ] ADRs document key decisions +- [ ] No circular dependencies + +**Scalability:** +- [ ] Feature modules independent +- [ ] Services properly scoped +- [ ] State management planned +- [ ] Testing strategy defined + +--- + +## Behavior Guidelines + +When assisting with architecture: + +1. **ALWAYS start with routes** - Never jump to components first +2. **Ask about team size** - Architecture depends on team scale +3. **Consider performance** - Bundle sizes and lazy loading +4. **Plan for growth** - Design for 10x scale +5. **Document decisions** - Use ADRs for key choices +6. **Validate structure** - Check against best practices +7. **Suggest alternatives** - Discuss trade-offs +8. **Emphasize simplicity** - Don't over-engineer for small teams + +--- + +## Common Patterns + +### Feature Module Pattern + +```typescript +// Feature structure +feature-name/ +├── components/ # All components +├── services/ # Feature services +├── models/ # Feature interfaces +├── feature.routes.ts # Feature routing +└── feature.component.ts # Container component +``` + +### Smart/Dumb Pattern + +```typescript +// Smart component (container) +@Component({ + selector: 'app-user-list', + template: ` + @for (user of users(); track user.id) { + + } + ` +}) +export class UserListComponent { + users = signal([]); + + constructor(private userService: UserService) {} + + deleteUser(id: string) { + this.userService.delete(id).subscribe(); + } +} + +// Dumb component (presentational) +@Component({ + selector: 'app-user-card', + template: `
{{ user.name }}
` +}) +export class UserCardComponent { + @Input({ required: true }) user!: User; + @Output() delete = new EventEmitter(); +} +``` + +--- + +## Summary + +As the Angular Architect, you: +- ✅ Design routes before components (Router-First) +- ✅ Plan scalable folder structures +- ✅ Enforce lazy loading for performance +- ✅ Separate concerns (core/shared/features) +- ✅ Document architectural decisions +- ✅ Consider team size and growth +- ✅ Optimize for maintainability and performance diff --git a/commands/design-routes.md b/commands/design-routes.md new file mode 100644 index 0000000..a59e1a4 --- /dev/null +++ b/commands/design-routes.md @@ -0,0 +1,261 @@ +--- +name: design-routes +description: Design and plan Angular routes using Router-First methodology before implementing components +--- + +Design the complete routing structure for an Angular application using Router-First methodology. + +## What This Command Does + +Following Doguhan Uluca's Router-First approach, this command helps you: +- ✅ Define routes BEFORE building components +- ✅ Plan lazy loading strategy +- ✅ Set up route guards and resolvers +- ✅ Design navigation hierarchy +- ✅ Plan data loading strategies + +## Information Needed + +Ask the user: +1. **App type** - What kind of application? (e.g., e-commerce, dashboard, CMS) +2. **User roles** - What roles exist? (e.g., admin, user, guest) +3. **Main features** - List all features (e.g., dashboard, products, orders, settings) +4. **Sub-features** - Any nested routes? (e.g., products → list/detail/edit) +5. **Public vs Protected** - Which routes need authentication? + +## Router-First Process + +### Step 1: Create User Flow Diagram +``` +Guest → Login → Dashboard → [Features] + ├── Products + ├── Orders + ├── Users (admin only) + └── Settings +``` + +### Step 2: Design Route Tree +``` +/ (redirect to /dashboard) +/login (public, lazy) +/dashboard (protected, lazy) + ├── /overview (default) + ├── /analytics (lazy) +/products (protected, lazy) + ├── /list (default) + ├── /detail/:id (lazy) + ├── /create (lazy, admin only) +/orders (protected, lazy) +/users (protected, lazy, admin only) +/settings (protected, lazy) +``` + +### Step 3: Define Route Configuration +```typescript +export const routes: Routes = [ + { + path: '', + redirectTo: '/dashboard', + pathMatch: 'full' + }, + { + path: 'login', + loadComponent: () => import('./features/auth/login.component') + .then(m => m.LoginComponent), + data: { breadcrumb: 'Login' } + }, + { + path: 'dashboard', + loadChildren: () => import('./features/dashboard/dashboard.routes') + .then(m => m.DASHBOARD_ROUTES), + canActivate: [AuthGuard], + data: { breadcrumb: 'Dashboard' } + }, + { + path: 'products', + loadChildren: () => import('./features/products/products.routes') + .then(m => m.PRODUCTS_ROUTES), + canActivate: [AuthGuard], + data: { breadcrumb: 'Products' } + }, + { + path: 'users', + loadChildren: () => import('./features/users/users.routes') + .then(m => m.USERS_ROUTES), + canActivate: [AuthGuard, RoleGuard], + data: { breadcrumb: 'Users', role: 'admin' } + } +]; +``` + +### Step 4: Plan Guards and Resolvers +```typescript +// Guards needed +AuthGuard - Check if user is logged in +RoleGuard - Check user permissions +UnsavedGuard - Warn before leaving unsaved forms + +// Resolvers needed (optional) +UserResolver - Load user data before route activates +ProductResolver - Preload product details +``` + +### Step 5: Loading Strategy +``` +Immediate Load (Critical Path): +- Login page +- Dashboard shell +- Error pages + +Lazy Load (On Demand): +- Dashboard sub-routes +- Product management +- Admin features +- Reports +- Settings + +Preload Strategy: +- Dashboard components (after login) +- High-traffic features +``` + +## Output Format + +Provide: + +1. **Visual Route Tree** - ASCII diagram of all routes +2. **Route Configuration Code** - Complete app.routes.ts +3. **Feature Routes** - Example feature routing files +4. **Guards Needed** - List with implementation stubs +5. **Performance Plan** - Bundle size estimates +6. **Navigation Strategy** - How users move through the app +7. **ADR Document** - Architectural Decision Record + +## Example Output + +### Route Tree Visualization +``` +Application Routes (Lazy-Loaded) +│ +├─ / (redirect to /dashboard) +│ +├─ /login (public) +│ +├─ /dashboard (protected, AuthGuard) +│ ├─ /overview (default) +│ ├─ /analytics +│ └─ /reports +│ +├─ /products (protected, AuthGuard) +│ ├─ /list (default) +│ ├─ /detail/:id +│ ├─ /create (admin only) +│ └─ /edit/:id (admin only) +│ +├─ /orders (protected, AuthGuard) +│ ├─ /list (default) +│ └─ /detail/:id +│ +├─ /users (protected, AuthGuard + RoleGuard) +│ ├─ /list (default) +│ ├─ /create +│ └─ /edit/:id +│ +├─ /settings (protected, AuthGuard) +│ +└─ /** (redirect to /dashboard) +``` + +### Bundle Size Planning +``` +Initial Bundle: 180 KB (gzipped) +- Core services +- Layout components +- Router + +Feature Bundles: +- Dashboard: 45 KB +- Products: 60 KB (data grid) +- Orders: 35 KB +- Users: 40 KB +- Settings: 25 KB + +Total: ~385 KB (all features loaded) +``` + +## Best Practices + +When designing routes: +- ✅ Keep URLs clean and RESTful +- ✅ Use kebab-case for paths +- ✅ Lazy load everything except critical path +- ✅ Group related features +- ✅ Plan for future features +- ✅ Add breadcrumb data +- ✅ Consider SEO (if applicable) +- ✅ Handle 404s gracefully + +## Common Patterns + +### Nested Routes +```typescript +{ + path: 'products', + component: ProductsLayoutComponent, + children: [ + { path: '', component: ProductListComponent }, + { path: ':id', component: ProductDetailComponent }, + { path: ':id/edit', component: ProductEditComponent } + ] +} +``` + +### Route Guards +```typescript +{ + path: 'admin', + canActivate: [AuthGuard, AdminGuard], + canDeactivate: [UnsavedChangesGuard], + children: [...] +} +``` + +### Route Data +```typescript +{ + path: 'dashboard', + data: { + breadcrumb: 'Dashboard', + title: 'Dashboard - My App', + animation: 'DashboardPage' + } +} +``` + +## Usage + +```bash +# In Claude Code +/angular-architecture:design-routes + +# Or natural language +"Design routes for an e-commerce app with products, cart, checkout, and admin" +"Use angular-architect to plan routing for a real estate listing platform" +``` + +## Deliverables + +After running this command, you'll have: +1. Complete route structure documented +2. Code for app.routes.ts +3. Feature route files +4. Guard implementations +5. Performance plan +6. Next implementation steps + +## Notes + +- Routes should tell a story of your app's features +- Design for scalability (easy to add new features) +- Consider analytics tracking in routing +- Plan error handling routes (404, 403, 500) diff --git a/commands/scaffold-project.md b/commands/scaffold-project.md new file mode 100644 index 0000000..868d81a --- /dev/null +++ b/commands/scaffold-project.md @@ -0,0 +1,142 @@ +--- +name: scaffold-project +description: Generate complete Angular 17+ project structure with Router-First architecture, lazy loading, and enterprise patterns +--- + +Generate a production-ready Angular 17+ project structure following Router-First methodology. + +## What You'll Create + +A complete enterprise Angular project with: +- ✅ Router-First architecture +- ✅ Lazy-loaded feature modules +- ✅ Core module (singletons) +- ✅ Shared module (reusables) +- ✅ TypeScript strict mode +- ✅ Standalone components +- ✅ Proper folder organization + +## Project Information Needed + +Ask the user for: +1. **Project name** - kebab-case (e.g., `my-enterprise-app`) +2. **Key features** - What features does the app need? (e.g., dashboard, users, reports) +3. **Team size** - Small (2-5), Medium (5-20), Large (20+) +4. **Authentication needed?** - Yes/No +5. **Additional requirements** - Any special needs (GIS, real-time, offline)? + +## Generation Steps + +1. **Create root structure** with app.routes.ts, app.config.ts +2. **Set up core module** with services, guards, interceptors +3. **Create shared module** with common components +4. **Generate feature modules** with lazy loading +5. **Configure TypeScript** with strict mode +6. **Set up routing** with guards and data +7. **Add layout components** (header, sidebar, footer) +8. **Generate example components** for each feature + +## Example Output Structure + +``` +src/app/ +├── core/ +│ ├── services/ +│ │ ├── auth.service.ts +│ │ └── api.service.ts +│ ├── guards/ +│ │ └── auth.guard.ts +│ ├── interceptors/ +│ │ └── auth.interceptor.ts +│ └── models/ +│ └── user.interface.ts +│ +├── shared/ +│ ├── components/ +│ │ ├── loading-spinner/ +│ │ └── error-message/ +│ └── pipes/ +│ └── time-ago.pipe.ts +│ +├── features/ +│ ├── dashboard/ +│ │ ├── components/ +│ │ ├── dashboard.routes.ts +│ │ └── dashboard.component.ts +│ └── users/ +│ ├── components/ +│ ├── users.routes.ts +│ └── users.component.ts +│ +├── layout/ +│ ├── header/ +│ ├── sidebar/ +│ └── main-layout.component.ts +│ +├── app.routes.ts +├── app.config.ts +└── app.component.ts +``` + +## Configuration Files to Generate + +### tsconfig.json (strict mode) +```json +{ + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noUnusedLocals": true, + "noUnusedParameters": true + } +} +``` + +### angular.json (performance budgets) +```json +{ + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + } + ] +} +``` + +## Best Practices to Include + +- All components use `standalone: true` +- All routes use lazy loading except root +- Separate files for templates/styles (no inline) +- Guards use functional style (Angular 14+) +- Services use `inject()` function +- Components use signals for state + +## Output Format + +Provide: +1. **Complete folder tree** showing all files +2. **Key file contents** (app.routes.ts, core services, example components) +3. **Installation commands** for dependencies +4. **Next steps** for development +5. **Architecture decision rationale** - Why this structure? + +## Usage + +```bash +# In Claude Code +/angular-architecture:scaffold-project + +# Or natural language +"Use angular-architect to scaffold a new e-commerce project with 4 features" +``` + +## Notes + +- Adapt complexity based on team size +- Small teams: simpler structure +- Large teams: more abstraction and tooling +- Always document architectural decisions in README diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..07521b2 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,61 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:EhssanAtassi/angular-marketplace-developer:plugins/angular-architecture", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "1fa4b52d5629adf0fc72b3064a6be4ce5812399e", + "treeHash": "08a66199efdaf0f4d38c0d65ee7ef576150fd780f61a24f0201213538d703539", + "generatedAt": "2025-11-28T10:10:27.842514Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "angular-architecture", + "description": "Router-First architecture, enterprise patterns, modular design with lazy loading", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "4d8d4bab76f1ec03e784140ed9662bc2dfa4e5a7b74746333f68cdda3849f65b" + }, + { + "path": "agents/angular-architect.md", + "sha256": "93331b82c64326c28077665cf3e6e51579ade43ebbc84ee3f7ae38ef57052a25" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "33712575f5c70e72852d4fa3cb203fc07a970754de3ab17b97ba7f2ba12cede4" + }, + { + "path": "commands/design-routes.md", + "sha256": "364c740021db4a72be0ab48e8b4259c8f25be1cda67d710ec7df3545505990f9" + }, + { + "path": "commands/scaffold-project.md", + "sha256": "f8f31ea6e88552ebf47cc95af691b143da0fe35fd74d961911e705e9a1d4d373" + }, + { + "path": "skills/enterprise-patterns/SKILL.md", + "sha256": "d5e4473aef7942781ccdf721110de19611a67fec4b6d29c9d815e290210c9230" + }, + { + "path": "skills/router-first-methodology/SKILL.md", + "sha256": "f38c963d6e8bf407ff81e70d1218ddd5c5af7c58801e1a5c8cf82aa88bbed83e" + } + ], + "dirSha256": "08a66199efdaf0f4d38c0d65ee7ef576150fd780f61a24f0201213538d703539" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/enterprise-patterns/SKILL.md b/skills/enterprise-patterns/SKILL.md new file mode 100644 index 0000000..524cd50 --- /dev/null +++ b/skills/enterprise-patterns/SKILL.md @@ -0,0 +1,693 @@ +# Enterprise Angular Patterns + +Proven architectural patterns for building scalable Angular applications in enterprise environments with teams of 5-100+ developers. + +--- + +## Core Principles + +1. **Separation of Concerns** - Each piece of code has one responsibility +2. **Single Source of Truth** - State lives in one place +3. **Consistency** - Follow patterns religiously +4. **Scalability** - Design for 10x growth +5. **Maintainability** - Code should be easy to change + +--- + +## Pattern 1: Core-Shared-Features Structure + +### Overview +Organize code into three main categories based on scope and reusability. + +### The Three Folders + +``` +src/app/ +├── core/ # App-wide singletons (loaded once) +├── shared/ # Reusable components/utilities +└── features/ # Feature modules (lazy loaded) +``` + +### Core Module Rules + +**What belongs in core:** +- ✅ Singleton services (AuthService, ApiService, CacheService) +- ✅ HTTP interceptors (auth, error handling, retry) +- ✅ Route guards (authentication, authorization) +- ✅ Global error handlers +- ✅ App-wide models and interfaces +- ✅ Constants and configuration + +**What does NOT belong:** +- ❌ UI components +- ❌ Feature-specific services +- ❌ Reusable utilities (those go in shared) + +**Example:** +```typescript +// core/services/auth.service.ts +@Injectable({ providedIn: 'root' }) +export class AuthService { + private currentUser$ = new BehaviorSubject(null); + + login(credentials: Credentials): Observable { + return this.http.post('/api/auth/login', credentials).pipe( + tap(user => this.currentUser$.next(user)) + ); + } + + getCurrentUser(): Observable { + return this.currentUser$.asObservable(); + } +} +``` + +### Shared Module Rules + +**What belongs in shared:** +- ✅ Dumb/presentational components (buttons, cards, modals) +- ✅ Custom directives (tooltips, permissions, auto-focus) +- ✅ Custom pipes (formatting, filtering) +- ✅ Utility functions (date helpers, validators) +- ✅ Common interfaces used across features + +**What does NOT belong:** +- ❌ Business logic +- ❌ HTTP calls +- ❌ Feature-specific components + +**Example:** +```typescript +// shared/components/data-table/data-table.component.ts +@Component({ + selector: 'app-data-table', + standalone: true, + template: ` + + + + @for (column of columns(); track column.key) { + + } + + + + @for (row of data(); track row.id) { + + @for (column of columns(); track column.key) { + + } + + } + +
{{ column.label }}
{{ row[column.key] }}
+ ` +}) +export class DataTableComponent { + columns = input.required(); + data = input.required(); +} +``` + +### Features Module Rules + +**What belongs in features:** +- ✅ Feature-specific components (smart + dumb) +- ✅ Feature-specific services +- ✅ Feature-specific models +- ✅ Feature routing configuration + +**Structure:** +``` +features/ +└── products/ + ├── components/ # Feature components + │ ├── product-list/ + │ ├── product-detail/ + │ └── product-form/ + ├── services/ # Feature services + │ └── product.service.ts + ├── models/ # Feature models + │ └── product.interface.ts + ├── products.routes.ts # Feature routes + └── products.component.ts # Container component +``` + +--- + +## Pattern 2: Smart and Dumb Components + +### Overview +Separate components that manage data (smart) from components that display data (dumb). + +### Smart Components (Containers) + +**Characteristics:** +- Communicate with services +- Manage state +- Handle business logic +- Usually top-level feature components + +**Example:** +```typescript +// features/products/product-list.component.ts +@Component({ + selector: 'app-product-list', + template: ` + + + @if (loading()) { + + } @else if (error()) { + + } @else { + @for (product of products(); track product.id) { + + } + } + ` +}) +export class ProductListComponent { + private productService = inject(ProductService); + + products = signal([]); + loading = signal(false); + error = signal(null); + + ngOnInit() { + this.loadProducts(); + } + + loadProducts() { + this.loading.set(true); + this.productService.getProducts().pipe( + takeUntilDestroyed() + ).subscribe({ + next: products => { + this.products.set(products); + this.loading.set(false); + }, + error: err => { + this.error.set(err.message); + this.loading.set(false); + } + }); + } + + handleEdit(id: string) { + this.router.navigate(['/products', id, 'edit']); + } + + handleDelete(id: string) { + if (confirm('Delete this product?')) { + this.productService.delete(id).subscribe(); + } + } +} +``` + +### Dumb Components (Presentational) + +**Characteristics:** +- Receive data via @Input or input() +- Emit events via @Output or output() +- No service dependencies +- Highly reusable +- Easy to test + +**Example:** +```typescript +// shared/components/product-card.component.ts +@Component({ + selector: 'app-product-card', + standalone: true, + imports: [CurrencyPipe], + template: ` +
+ +

{{ product().name }}

+

{{ product().price | currency }}

+
+ + +
+
+ ` +}) +export class ProductCardComponent { + product = input.required(); + edit = output(); + delete = output(); +} +``` + +--- + +## Pattern 3: Service Layer Architecture + +### Overview +Organize services by responsibility: data access, business logic, and state management. + +### Data Services + +**Purpose:** HTTP communication only + +```typescript +// core/services/api.service.ts +@Injectable({ providedIn: 'root' }) +export class ApiService { + private http = inject(HttpClient); + private baseUrl = environment.apiUrl; + + get(endpoint: string): Observable { + return this.http.get(`${this.baseUrl}/${endpoint}`); + } + + post(endpoint: string, data: any): Observable { + return this.http.post(`${this.baseUrl}/${endpoint}`, data); + } +} +``` + +### Business Services + +**Purpose:** Business logic and domain operations + +```typescript +// features/products/services/product.service.ts +@Injectable({ providedIn: 'root' }) +export class ProductService { + private api = inject(ApiService); + + getProducts(): Observable { + return this.api.get('products').pipe( + map(products => this.enrichProducts(products)) + ); + } + + private enrichProducts(products: Product[]): Product[] { + return products.map(p => ({ + ...p, + displayPrice: this.formatPrice(p.price), + inStock: p.quantity > 0 + })); + } + + private formatPrice(price: number): string { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(price); + } +} +``` + +### State Services + +**Purpose:** Manage application state + +```typescript +// features/cart/services/cart-state.service.ts +@Injectable({ providedIn: 'root' }) +export class CartStateService { + private itemsSubject = new BehaviorSubject([]); + + // Public observable + items$ = this.itemsSubject.asObservable(); + + // Computed values + total$ = this.items$.pipe( + map(items => items.reduce((sum, item) => sum + item.price * item.quantity, 0)) + ); + + itemCount$ = this.items$.pipe( + map(items => items.reduce((count, item) => count + item.quantity, 0)) + ); + + addItem(item: CartItem) { + const current = this.itemsSubject.value; + this.itemsSubject.next([...current, item]); + } + + removeItem(id: string) { + const current = this.itemsSubject.value; + this.itemsSubject.next(current.filter(item => item.id !== id)); + } + + clear() { + this.itemsSubject.next([]); + } +} +``` + +--- + +## Pattern 4: Facade Pattern + +### Overview +Create a single entry point for complex subsystems. + +### Use Case +When a feature has multiple related services that components need to interact with. + +**Example:** +```typescript +// features/checkout/services/checkout.facade.ts +@Injectable({ providedIn: 'root' }) +export class CheckoutFacade { + private cartService = inject(CartService); + private paymentService = inject(PaymentService); + private shippingService = inject(ShippingService); + private orderService = inject(OrderService); + + // Expose simplified API + cart$ = this.cartService.items$; + total$ = this.cartService.total$; + shippingMethods$ = this.shippingService.getMethods(); + + processCheckout(data: CheckoutData): Observable { + return this.validateCart().pipe( + switchMap(() => this.calculateShipping(data.shippingMethod)), + switchMap(shipping => this.processPayment(data.payment, shipping)), + switchMap(payment => this.createOrder({ ...data, payment })), + tap(() => this.cartService.clear()) + ); + } + + private validateCart(): Observable { + return this.cart$.pipe( + take(1), + map(items => items.length > 0), + tap(valid => { if (!valid) throw new Error('Cart is empty'); }) + ); + } + + private calculateShipping(method: string): Observable { + return this.shippingService.calculate(method); + } + + private processPayment(payment: PaymentInfo, shipping: number): Observable { + return this.total$.pipe( + take(1), + switchMap(total => this.paymentService.charge({ + ...payment, + amount: total + shipping + })) + ); + } + + private createOrder(data: OrderData): Observable { + return this.orderService.create(data); + } +} + +// Component uses facade instead of multiple services +@Component({...}) +export class CheckoutComponent { + private facade = inject(CheckoutFacade); + + cart$ = this.facade.cart$; + total$ = this.facade.total$; + + checkout(data: CheckoutData) { + this.facade.processCheckout(data).subscribe({ + next: order => this.router.navigate(['/order-confirmation', order.id]), + error: err => this.showError(err) + }); + } +} +``` + +--- + +## Pattern 5: Error Handling Strategy + +### Global Error Handler + +```typescript +// core/handlers/global-error.handler.ts +@Injectable() +export class GlobalErrorHandler implements ErrorHandler { + private logger = inject(LoggerService); + private notification = inject(NotificationService); + + handleError(error: Error | HttpErrorResponse) { + if (error instanceof HttpErrorResponse) { + // Server error + this.handleHttpError(error); + } else { + // Client error + this.handleClientError(error); + } + } + + private handleHttpError(error: HttpErrorResponse) { + const message = this.getErrorMessage(error); + this.notification.showError(message); + this.logger.error('HTTP Error', { error, url: error.url }); + } + + private handleClientError(error: Error) { + this.notification.showError('An unexpected error occurred'); + this.logger.error('Client Error', { error, stack: error.stack }); + } + + private getErrorMessage(error: HttpErrorResponse): string { + if (error.status === 0) { + return 'No internet connection'; + } else if (error.status === 401) { + return 'Session expired. Please login again.'; + } else if (error.status === 403) { + return 'You do not have permission to perform this action'; + } else if (error.status >= 500) { + return 'Server error. Please try again later.'; + } + return error.error?.message || 'An error occurred'; + } +} +``` + +### HTTP Error Interceptor + +```typescript +// core/interceptors/error.interceptor.ts +export const errorInterceptor: HttpInterceptorFn = (req, next) => { + return next(req).pipe( + catchError((error: HttpErrorResponse) => { + if (error.status === 401) { + // Redirect to login + inject(Router).navigate(['/login']); + } + return throwError(() => error); + }) + ); +}; +``` + +--- + +## Pattern 6: Feature Flags + +### Overview +Control feature visibility without deploying new code. + +```typescript +// core/services/feature-flag.service.ts +@Injectable({ providedIn: 'root' }) +export class FeatureFlagService { + private flags = signal>({ + 'new-dashboard': false, + 'beta-checkout': true, + 'admin-analytics': false + }); + + isEnabled(feature: string): boolean { + return this.flags()[feature] ?? false; + } + + enable(feature: string) { + this.flags.update(flags => ({ ...flags, [feature]: true })); + } + + disable(feature: string) { + this.flags.update(flags => ({ ...flags, [feature]: false })); + } +} + +// Usage in component +@Component({ + template: ` + @if (showNewDashboard()) { + + } @else { + + } + ` +}) +export class DashboardComponent { + private featureFlags = inject(FeatureFlagService); + showNewDashboard = computed(() => this.featureFlags.isEnabled('new-dashboard')); +} + +// Usage in routes +{ + path: 'beta', + loadComponent: () => import('./beta.component'), + canActivate: [() => inject(FeatureFlagService).isEnabled('beta-features')] +} +``` + +--- + +## Pattern 7: Caching Strategy + +### Service-Level Cache + +```typescript +// core/services/cache.service.ts +@Injectable({ providedIn: 'root' }) +export class CacheService { + private cache = new Map(); + private TTL = 5 * 60 * 1000; // 5 minutes + + get(key: string): T | null { + const cached = this.cache.get(key); + if (!cached) return null; + + if (Date.now() - cached.timestamp > this.TTL) { + this.cache.delete(key); + return null; + } + + return cached.data as T; + } + + set(key: string, data: any) { + this.cache.set(key, { data, timestamp: Date.now() }); + } + + clear(key?: string) { + if (key) { + this.cache.delete(key); + } else { + this.cache.clear(); + } + } +} + +// Usage in service +@Injectable({ providedIn: 'root' }) +export class ProductService { + private cache = inject(CacheService); + private api = inject(ApiService); + + getProducts(): Observable { + const cached = this.cache.get('products'); + if (cached) { + return of(cached); + } + + return this.api.get('products').pipe( + tap(products => this.cache.set('products', products)) + ); + } +} +``` + +--- + +## Pattern 8: Loading States + +### Unified Loading Pattern + +```typescript +// core/models/loading-state.interface.ts +export interface LoadingState { + loading: boolean; + data: T | null; + error: string | null; +} + +// Feature service +@Injectable({ providedIn: 'root' }) +export class ProductService { + private state = signal>({ + loading: false, + data: null, + error: null + }); + + state$ = computed(() => this.state()); + + loadProducts() { + this.state.update(s => ({ ...s, loading: true, error: null })); + + this.api.get('products').subscribe({ + next: data => this.state.set({ loading: false, data, error: null }), + error: err => this.state.set({ loading: false, data: null, error: err.message }) + }); + } +} + +// Component +@Component({ + template: ` + @if (state().loading) { + + } @else if (state().error) { + + } @else if (state().data) { + @for (product of state().data; track product.id) { + + } + } + ` +}) +export class ProductListComponent { + private service = inject(ProductService); + state = this.service.state$; + + ngOnInit() { + this.service.loadProducts(); + } +} +``` + +--- + +## Team Structure Patterns + +### Pattern 1: Feature Teams +- Each team owns complete features +- Vertical slice (UI + API + DB) +- Autonomous deployment +- Best for: Medium to large teams (10-50+) + +### Pattern 2: Layer Teams +- Frontend team, backend team +- Horizontal slice +- Coordinated deployment +- Best for: Small teams (5-10) + +### Pattern 3: Component Teams +- Shared component library team +- Feature teams consume components +- Hybrid approach +- Best for: Large organizations (50+) + +--- + +## Summary + +Enterprise patterns ensure: +- ✅ Consistent codebase across large teams +- ✅ Predictable structure for new developers +- ✅ Separation of concerns +- ✅ Testable, maintainable code +- ✅ Scalability from day one + +**Key Takeaway:** Patterns create consistency. Consistency enables scale. diff --git a/skills/router-first-methodology/SKILL.md b/skills/router-first-methodology/SKILL.md new file mode 100644 index 0000000..904a353 --- /dev/null +++ b/skills/router-first-methodology/SKILL.md @@ -0,0 +1,471 @@ +# 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.