PFC #1 - Page-Feature Composition: The Frontend Philosophy That Actually Works
Why Angular projects turn into spaghetti and how to fix it
PFC #1 - Page-Feature Composition: The Frontend Philosophy That Actually Works
Why Angular projects turn into spaghetti and how to fix it
TL;DR
Traditional Angular projects tend to become spaghetti-like as they grow. Components get tightly coupled to contexts. Copy-paste becomes the norm. PFC fixes this by separating Features (business logic) from Pages (orchestration). Result: truly reusable features, cleaner code, happier teams. While this series focuses on Angular, the core concepts apply to React, Vue, and any other component-based framework.
Six months ago, your Angular project was a thing of beauty. Clean components, organized services, everything in its place. Today? You’re afraid to touch anything because changing one component might break three others you didn’t know existed.
Sound familiar? You’re not alone. Every Angular developer has been there.
If that describes your current situation, you’re about to meet your new best friend: Page-Feature Composition. It’s not rocket science, it’s not revolutionary. It’s just a sensible way to organize frontend apps that actually scales.
Note: This series uses Angular and TypeScript for examples, but the architecture patterns work with React, Vue, Svelte, or any component-based framework. The principles are framework-agnostic; the syntax is Angular.
The Philosophy
Stop fighting Angular and start working with it. The key insight is simple: stop asking “How do I make this component reusable everywhere?” and start asking “How do I make features composable on pages?”
The Problem We Keep Creating
// We’ve all written this
@Component({
selector: ‘app-user-card’,
template: `...`
})
export class UserCardComponent {
@Input() user: User;
constructor(
private userService: UserService,
private router: Router
) {}
editUser() {
this.router.navigate([’/admin/users/edit’, this.user.id]);
}
}This looks innocent enough. Until six months later, when you need the same card in a customer dashboard, but it navigates to the wrong route. Now you’re adding props to control routes, actions, and styling.
Welcome to prop hell. Population: your entire codebase.
The Mental Shift
The difference? Components try to be generic (and fail). Features are specific but composable. Pages orchestrate features into different contexts without modifying them.
Folder Structure
Here’s how PFC organizes an Angular project:
src/app/
├── features/ # Business logic lives here
│ ├── user-management/
│ ├── product-catalog/
│ └── order-processing/
├── pages/ # Orchestrators
│ ├── dashboard/
│ ├── admin/
│ └── customer-portal/
├── shared/ # Utilities
│ └── components/
└── core/ # App foundation
└── auth/
Features
Self-contained business capabilities. Everything related to “user management” lives in one folder. Components, services, state management, all of it. Think of features as mini-applications focused on a single business domain.
Pages
Conductors that compose features into user experiences but contain zero business logic. Just orchestration. A page arranges features on screen and handles navigation.
Shared
Truly generic stuff. Button components, pipes, utilities that work anywhere. If it doesn’t belong to a specific feature and isn’t page-specific, it should be placed here.
Core
App-wide concerns. Authentication, layout, configuration. Things that are truly global to your application.
Pages as Orchestrators
This is the key insight that makes everything fall into place. Pages don’t implement features. They compose them.
Think of a symphony orchestra. The conductor doesn’t play the instruments. They coordinate the musicians (features) to create a cohesive performance (user experience).
What You’re Probably Doing Now
Your page component contains over 200 lines of template code that mixes users, products, orders, and notifications. The TypeScript file contains over 50 properties and methods that handle everything.
It’s a kitchen sink. Nobody wants to touch it because changing one thing might break three others.
The PFC Way
// src/app/pages/dashboard/dashboard.page.ts
@Component({
templateUrl: ‘./dashboard.html’
})
export class DashboardPage { }<!-- src/app/pages/dashboard/dashboard.html -->
<div class=”dashboard-grid”>
<section class=”users-section”>
<h2>Team Overview</h2>
<app-user-management-container></app-user-management-container>
</section>
<section class=”products-section”>
<h2>Latest Products</h2>
<app-product-catalog-container></app-product-catalog-container>
</section>
</div>The page is almost empty. It just arranges features on the screen and adds page-specific layout.
User logic? Lives in the user-management feature.
Product logic? Lives in the product-catalog feature.
Dashboard layout? That’s the page’s only job.
Clean separation. Clear boundaries. No confusion about where code belongs.
Features Architecture
Each feature is a mini-application focused on one business domain. This is where the real work happens.
Internal Structure
features/user-management/
├── components/ # Dumb UI (user cards, forms)
├── containers/ # Smart orchestrators
├── services/ # API calls
├── store/ # State management (NgRx/signals)
├── models/ # TypeScript interfaces
├── user-management.facade.ts # Public API
├── user-management.module.ts
└── index.ts # What pages can useThe magic is in what you export vs what stays internal.
Public vs Private API
// src/app/features/user-management/index.ts
export * from ‘./user-management.module’;
export * from ‘./user-management.facade’;
// Everything else stays private:
// - Internal components
// - Services
// - Store details
// - Helper utilities
Pages can only access what you explicitly export. This prevents tight coupling and keeps your architecture clean.
The Facade Pattern
Each feature exposes a facade. It’s a simple API that pages can use to trigger feature actions programmatically.
// src/app/features/user-management/user-management.facade.ts
@Injectable()
export class UserManagementFacade {
readonly isLoading$ = this.storeFacade.isLoading$;
constructor(private storeFacade: UserStoreFacade) {}
loadUsers(): void {
this.storeFacade.loadUsers();
}
}Most of the time, pages use the container component and never interact with the facade. But it’s there when you need it (like programmatically refreshing data or responding to route changes).
Why This Works
After using PFC in production for over a year on real enterprise applications, here’s what we’ve learned:
For Your Code
True reusability. The user-management container works on the dashboard, admin panel, and customer portal. Same code, different contexts. No modifications needed.
Clear boundaries. No more “where does this file go?” debates. User stuff goes in
features/user-management/. The dashboard page goes inpages/dashboard/. Done.Easy testing. Test features in isolation with simple input and output values. Test pages as composition exercises. No massive mock setups required.
Predictable growth. Your codebase grows linearly with features, not exponentially. Adding feature 20 doesn’t make features 1-19 more complex.
For Your Team
Parallel development. Developer A builds the user-management feature. Developer B builds the product-catalog feature. Developer C composes them into pages. Minimal merge conflicts.
Fast onboarding. New developers understand the pattern in 10 minutes. “Features contain business logic. Pages compose features. That’s it.”
Scales with team size. Works great for solo developers (clear structure). Scales perfectly to large teams (clear ownership).
For Your Sanity
No prop explosion. Features handle their own modes and configurations internally. No 15+ optional props trying to make components “generic.”
No copy-paste hell. Build the feature once, compose it everywhere. The user-management feature is the same whether it’s on page 1 or page 10.
No modification fear. Changing a feature doesn’t randomly break unrelated pages. Clear boundaries mean predictable impact.
When NOT to Use PFC
Let’s be honest. This isn’t always the answer:
Skip it for:
Simple landing pages or marketing sites
Prototypes or short-term experiments
Solo projects under 10 routes
Apps with mostly unique, one-off interfaces
Use it for:
Medium to large applications (10+ routes)
Long-term projects that will evolve
Teams of 3+ developers
Apps with recurring UI patterns
What’s Next
This post introduced the philosophy and high-level structure. We’ll dive deeper in upcoming posts:
PFC #2 - Pages as Orchestrators
How to compose features without losing your mind. Real composition patterns, handling feature communication, and layout strategies.
PFC #3 - Building Bulletproof Features
Internal feature architecture. Containers vs components, the facade pattern, state management, and what to export vs keep private.
PFC #4 - From Monolith to Composition
Practical migration strategies. How to adopt PFC in existing projects without rewriting everything. Team workflows and common pitfalls.
PFC #5 - Real-World Examples
Complete feature and page implementations working together. Dashboard example, form flows, and handling complex scenarios.
Your Mission
Before the next post drops, audit your current Angular project:
Find your most complex page component. How many lines? How many different business concerns does it handle?
Count your component variations. How many “Card” components do you have? UserCard, ProductCard, CustomerCard, EmployeeCard...
Identify feature boundaries. What natural business groupings exist? What chunks of functionality could be self-contained?
Drop a comment and share what you find. Don’t be shy about the mess. We’ve all been there, and that’s precisely where PFC shines brightest.
Bonus challenge: Try to identify one feature in your current codebase that could be extracted using PFC principles. What would you put in the feature folder? What would the facade expose?
This is Part 1 of the Page-Feature Composition (PFC) series. Follow along at better-frontend.dev for practical Angular wisdom that keeps your projects clean and your coffee breaks peaceful.
Next up: PFC #2 - Pages as Orchestrators
This concludes the Page-Feature Composition series. Follow better-frontend.dev for more practical Angular architecture insights.
The Series:
- PFC #1 - The Philosophy ← You are here
- PFC #2 - Pages as Orchestrators
- PFC #3 - Building Features
- PFC #4 - Migration Strategies
- PFC #5 - Real-World Examples

