Press enter or click to view image in full size
As Angular applications grow, file structure stops being a cosmetic choice and becomes an architectural decision. A well-designed folder structure does more than keep your project tidy — it enforces boundaries, clarifies responsibilities, improves scalability, and prevents long-term technical debt. In this article, we’ll explore a practical and scalable Angular file structure that balances vertical feature slicing with a clear system core, making your codebase easier to maintain as your product evolves.
Example Structure
src/app/
core/ # singleton services / cross-cutting
config/
app-config.ts # injection token + config loader
auth/
auth.service.ts
auth.interceptor.ts
auth.guard.ts
http/
api-client.service.ts
error.interceptor.ts
access/
features.ts
entitlements.ts
can.ts # pure TS (kann auch in lib/)
access.guard.ts # Angular Guard nutzt can() shared/ # reusable UI building blocks
components/
button/
button.component.ts
modal/
modal.component.ts
directives/
autofocus.directive.ts
pipes/
money.pipe.ts
date-time.pipe.ts
shared.module.ts # optional (wenn du NgModules nutzt)
features/ # vertical slices / product areas
chat/
pages/
chat.page.ts
components/
message-list.component.ts
composer.component.ts
data-access/
chat.api.ts # calls backend
state/ # optional (NgRx/Signals store)
chat.store.ts
chat.routes.ts # lazy routes (Standalone) / chat-routing.module.ts
billing/
pages/
pricing.page.ts
components/
pricing-table.component.ts
upgrade-button.component.ts
data-access/
billing.api.ts
billing.routes.ts
app.routes.ts # top-level routes (lazy load features)
app.component.ts
High-Level Architectural Goals
This structure is optimized for:
- Scalability (multiple features can grow independently)
- Clear boundaries (cross-cutting vs feature logic)
- Lazy loading
- Testability
- Reduced coupling
- Long-term maintainability
It follows a Vertical Slice (feature-based) approach, combined with a Core layer for cross-cuting services.
Avoid generic folder names
Yes — avoid them. They describe what the code is, not what it does.
The problem with generic folders
services/
├── user.service.ts # what kind of service? does what?
├── chat.service.ts
├── stripe.service.ts
└── email.service.tsA new dev opening this has no idea what any of these do without reading them. The folder name adds zero information.
Same with:
controllers/ # are we even doing MVC? why?
helpers/ # helps with what?
utils/ # this one gets a pass — convention is strong enough
managers/ # Angular-era smell
handlers/ # too vagueThe alternative: name by responsibility
Instead of asking “what is this code?” ask “what does this code do?”
Generic Specific Why better services/stripe.service.ts core/billing/stripe.ts Location gives context services/email.service.ts core/notifications/email.ts Intent is clear services/user.service.ts core/db/queries/users.ts Exactly what it does controllers/chat.ts pages/api/chat/index.ts Astro route is the controller helpers/date.ts lib/date.ts Convention, not description
The one exception
utils/ gets a pass because the convention is universally understood in the JS ecosystem. Everyone knows utils/ means small pure helper functions. It has earned its generic name through ubiquity.
lib/ ✅ stateless, portable utilities
utils/ ✅ acceptable — convention is strong
services/ ❌ too vague — use location + filename instead
controllers/ ❌ not an MVC framework
helpers/ ❌ helps with what exactly?
managers/ ❌ Angular-era, avoid
handlers/ ❌ only acceptable for webhook/event handlers specificallyThe underlying principle
A good folder name makes the next folder name obvious.
core/billing/stripe.ts ← you know exactly what this is before opening it
core/billing/webhooks/ ← of course webhooks have their own folder
core/auth/guards.ts ← guards protect routes, clearvs.
services/stripe.service.ts ← is this billing? auth? notifications?The folder should answer: what domain does this belong to? The filename should answer: what does this specifically do? If you need a word like “service” or “controller” to explain what a file does — the folder structure isn’t doing its job.