angular-arch
Kurata's Angular architecture and organization patterns
SKILL.md
| Name | angular-arch |
| Description | Kurata's Angular architecture and organization patterns |
name: angular-arch description: "Angular architecture and organization patterns" allowed-tools: []
Deborah Kurata: Angular Architecture
Deborah Kurata's core belief: Well-organized code is maintainable code. Architecture decisions made early compound over the life of a project.
The Foundational Principle
"Structure your Angular application so that you can locate code quickly, identify what the code does at a glance, keep the flattest structure possible, and stay DRY."
The LIFT principle:
- Locate code quickly
- Identify code at a glance
- Flattest structure possible
- Try to stay DRY
Core Principles
1. Feature Module Organization
Organize by feature, not by type.
Not this (organize by type):
src/app/
├── components/
│ ├── user-list.component.ts
│ ├── product-list.component.ts
│ └── order-list.component.ts
├── services/
│ ├── user.service.ts
│ ├── product.service.ts
│ └── order.service.ts
└── models/
├── user.model.ts
├── product.model.ts
└── order.model.ts
This (organize by feature):
src/app/
├── users/
│ ├── user-list.component.ts
│ ├── user-detail.component.ts
│ ├── user.service.ts
│ ├── user.model.ts
│ └── users.module.ts
├── products/
│ ├── product-list.component.ts
│ ├── product.service.ts
│ └── products.module.ts
└── shared/
├── components/
├── pipes/
└── shared.module.ts
2. Module Types and Responsibilities
Five types of modules:
| Module Type | Purpose | Loads |
|---|---|---|
| Root (App) | Bootstrap | Once at startup |
| Feature | Business feature | Lazy loaded |
| Shared | Reusable components | Imported by features |
| Core | Singleton services | Once in AppModule |
| Routing | Route configuration | With parent module |
// Core module - singleton services, guards
@NgModule({
providers: [AuthService, LoggingService]
})
export class CoreModule {
constructor(@Optional() @SkipSelf() parent: CoreModule) {
if (parent) {
throw new Error('CoreModule already loaded. Import only in AppModule.');
}
}
}
// Shared module - reusable, no singletons
@NgModule({
declarations: [SpinnerComponent, TruncatePipe],
exports: [SpinnerComponent, TruncatePipe, CommonModule]
})
export class SharedModule {}
3. Smart vs Presentational Components
Clear separation of concerns.
CONTAINER (Smart) PRESENTATIONAL (Dumb)
───────────────── ─────────────────────
user-shell.component user-list.component
user-detail.component
user-form.component
Responsibilities: Responsibilities:
- Fetch data - Display data
- Handle state - Emit events
- Coordinate children - No service injection
- Route handling - Pure @Input/@Output
Naming convention:
feature/
├── feature-shell.component.ts # Smart container
├── feature-list.component.ts # Presentational
├── feature-detail.component.ts # Presentational
├── feature-edit.component.ts # Presentational
└── feature.service.ts
4. Barrel Files (index.ts)
Export public API, hide internals.
// users/index.ts
export * from './users.module';
export * from './user.model';
export * from './user.service';
// Don't export internal components
// Importing from outside the feature:
import { UserService, User } from '@app/users';
5. Path Aliases
Use TypeScript path aliases for clean imports.
tsconfig.json:
{
"compilerOptions": {
"paths": {
"@app/*": ["src/app/*"],
"@core/*": ["src/app/core/*"],
"@shared/*": ["src/app/shared/*"],
"@env/*": ["src/environments/*"]
}
}
}
Usage:
// Instead of: import { UserService } from '../../../core/services/user.service';
import { UserService } from '@core/services/user.service';
6. State Management Patterns
Choose complexity appropriate to your app.
COMPLEXITY LEVEL SOLUTION
──────────────── ────────
Simple Services + BehaviorSubject
Medium Component Store (@ngrx/component-store)
Complex Global Store (@ngrx/store)
Simple state (service-based):
@Injectable({ providedIn: 'root' })
export class UserStateService {
private usersSubject = new BehaviorSubject<User[]>([]);
users$ = this.usersSubject.asObservable();
setUsers(users: User[]) {
this.usersSubject.next(users);
}
}
Medium state (component store):
interface UserState {
users: User[];
loading: boolean;
}
@Injectable()
export class UserStore extends ComponentStore<UserState> {
readonly users$ = this.select(state => state.users);
readonly loading$ = this.select(state => state.loading);
readonly loadUsers = this.effect<void>(trigger$ =>
trigger$.pipe(
tap(() => this.patchState({ loading: true })),
switchMap(() => this.userService.getAll().pipe(
tapResponse(
users => this.patchState({ users, loading: false }),
error => this.patchState({ loading: false })
)
))
)
);
}
7. Route Organization
One routing module per feature, lazy loaded.
// app-routing.module.ts
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{
path: 'users',
loadChildren: () => import('./users/users.module').then(m => m.UsersModule)
},
{
path: 'products',
loadChildren: () => import('./products/products.module').then(m => m.ProductsModule)
},
{ path: '**', component: PageNotFoundComponent }
];
// users/users-routing.module.ts
const routes: Routes = [
{
path: '',
component: UserShellComponent,
children: [
{ path: '', component: UserListComponent },
{ path: ':id', component: UserDetailComponent },
{ path: ':id/edit', component: UserEditComponent }
]
}
];
8. Service Design Patterns
Services should be focused and testable.
// Data service - HTTP operations
@Injectable({ providedIn: 'root' })
export class UserDataService {
private url = '/api/users';
constructor(private http: HttpClient) {}
getAll(): Observable<User[]> {
return this.http.get<User[]>(this.url);
}
getById(id: number): Observable<User> {
return this.http.get<User>(`${this.url}/${id}`);
}
}
// Facade service - coordinates multiple data services
@Injectable({ providedIn: 'root' })
export class UserFacadeService {
constructor(
private userData: UserDataService,
private userState: UserStateService
) {}
loadUsers() {
this.userData.getAll().subscribe(users => {
this.userState.setUsers(users);
});
}
}
9. Interface Segregation
Small, focused interfaces over large ones.
// Not this - one big interface
interface User {
id: number;
name: string;
email: string;
address: Address;
orders: Order[];
preferences: Preferences;
// ... 20 more properties
}
// This - focused interfaces
interface UserBasic {
id: number;
name: string;
email: string;
}
interface UserWithAddress extends UserBasic {
address: Address;
}
interface UserWithOrders extends UserBasic {
orders: Order[];
}
10. Consistent Naming Conventions
ELEMENT NAMING CONVENTION
─────── ─────────────────
Feature module users.module.ts
Routing module users-routing.module.ts
Component user-list.component.ts
Service user.service.ts
Directive highlight.directive.ts
Pipe truncate.pipe.ts
Guard auth.guard.ts
Resolver user.resolver.ts
Interceptor logging.interceptor.ts
Model/Interface user.model.ts
The Kurata Test
Before committing architecture decisions, ask:
- Can I locate this code quickly? By feature, not by type?
- Can I identify what this does at a glance? Clear naming?
- Is the structure as flat as possible? No unnecessary nesting?
- Am I repeating myself? Should this be in SharedModule?
- Is this module focused? One responsibility?
- Is state management appropriate? Not over-engineered?
When Reviewing Code
Apply these checks:
- Features organized in feature modules
- Core services in CoreModule (imported once)
- Shared components in SharedModule
- Smart/presentational component separation
- Lazy loading for feature modules
- Path aliases for clean imports
- Barrel files for public API
- Consistent naming conventions
- Appropriate state management complexity
When NOT to Use This Skill
Use a different skill when:
- Designing DI and testability → Use
angular-core - Optimizing performance → Use
angular-perf - Working with RxJS → Use
rxjs - Applying design patterns → Use
design-patterns
Kurata is the Angular organization skill—use it for project structure, module design, and architecture patterns.
Sources
- Kurata, "Angular Architecture: Best Practices" (Pluralsight)
- Kurata, "RxJS and Reactive Forms in Angular" (Pluralsight)
- Angular Style Guide (influenced by Kurata's work)
- Kurata's conference talks and blog posts
"Good architecture is not about choosing the right technology—it's about organizing code so your team can work effectively." — Deborah Kurata