Feature-Based Project Structure — Organising a Large Angular App

A well-organised Angular project structure makes the codebase navigable for teams, ensures features can be developed and tested independently, and keeps shared code discoverable. The feature-based structure — where all files related to a feature (component, service, model, test) live together under features/{feature-name}/ — is the Angular team’s recommendation for medium-to-large applications. The alternative (type-based structure: all components in one folder, all services in another) becomes unwieldy as the application grows beyond a few features.

// ── Feature-based structure for the BlogApp ───────────────────────────────
// src/app/
// ├── core/                            ← singleton services (once per app)
// │   ├── interceptors/
// │   │   ├── auth.interceptor.ts      ← adds JWT to requests
// │   │   └── error.interceptor.ts     ← handles 401, 500 globally
// │   ├── guards/
// │   │   ├── auth.guard.ts            ← requires authentication
// │   │   └── admin.guard.ts           ← requires Admin role
// │   └── services/
// │       └── auth.service.ts          ← login, logout, token state
// │
// ├── features/                        ← feature-specific code
// │   ├── posts/
// │   │   ├── post-list.component.ts   ← /posts route
// │   │   ├── post-detail.component.ts ← /posts/:slug route
// │   │   ├── post-create.component.ts ← /posts/new route
// │   │   └── posts.service.ts         ← HTTP calls for posts
// │   ├── auth/
// │   │   ├── login.component.ts
// │   │   ├── register.component.ts
// │   │   └── auth.service.ts          ← login/register HTTP calls
// │   └── admin/
// │       ├── admin-dashboard.component.ts
// │       └── admin.service.ts
// │
// ├── shared/                          ← reusable across features
// │   ├── components/
// │   │   ├── loading-spinner.component.ts
// │   │   └── error-message.component.ts
// │   ├── pipes/
// │   │   └── time-ago.pipe.ts
// │   └── directives/
// │       └── click-outside.directive.ts
// │
// └── models/                          ← TypeScript interfaces matching API DTOs
//     ├── post.ts                      ← PostDto, PostSummaryDto, CreatePostRequest
//     ├── auth.ts                      ← AuthResponse, LoginRequest, RegisterRequest
//     └── pagination.ts                ← PagedResult

// ── Model interfaces — mirror ASP.NET Core DTOs ───────────────────────────
// src/app/models/post.ts
export interface PostSummaryDto {
  id:           number;
  title:        string;
  slug:         string;
  authorName:   string;
  publishedAt:  string;
  viewCount:    number;
  commentCount: number;
  tags:         string[];
}

export interface PagedResult<T> {
  items:       T[];
  page:        number;
  pageSize:    number;
  total:       number;
  totalPages:  number;
  hasNextPage: boolean;
  hasPrevPage: boolean;
}
Note: The core/ folder contains services and providers that should be instantiated once for the entire application — authentication, HTTP interceptors, and app-wide error handling. Services in core/ are typically provided at the root level with providedIn: 'root'. The shared/ folder contains components, pipes, and directives that are reused across multiple features but are not singletons. The features/ folder contains everything related to a specific domain — components, services, models, and guards that belong to that feature.
Tip: Configure TypeScript path aliases in tsconfig.json to avoid fragile relative imports. With "@models/*": ["src/app/models/*"], every component imports models as import { PostDto } from '@models/post' instead of import { PostDto } from '../../../models/post'. Relative paths break when components are moved; path aliases always resolve from the project root. Add the same paths to angular.json under compilerOptions for the build to resolve them correctly.
Warning: Avoid circular dependencies between features — Feature A importing from Feature B and Feature B importing from Feature A causes build errors and architectural debt. If two features share code, extract the shared part to shared/ and import from there. If the shared code is complex enough, consider creating a separate feature folder for it (e.g., features/notifications/ instead of having both posts/ and auth/ import notification components directly).

Common Mistakes

Mistake 1 — Type-based structure for large apps (all components in one folder)

❌ Wrong — components/ folder with 30 unrelated components; hard to find feature-related code.

✅ Correct — feature-based structure: all post-related code in features/posts/.

Mistake 2 — Importing models from other feature folders (cross-feature coupling)

❌ Wrong — auth/login.component.ts imports PostDto from features/posts/models/.

✅ Correct — shared interfaces in models/ at app level; feature-specific models stay in the feature folder.

🧠 Test Yourself

A LoadingSpinnerComponent is used in both the posts list and the auth login form. Where should it live in the project structure?