PFC #4 - Page-Feature Composition: From Monolith to Composition
Practical migration strategies without rewriting everything
TL;DR
You don’t need to rewrite your entire app to adopt PFC. Start with one new feature or refactor one messy page. Use the strangler pattern: build new features with PFC, gradually extract from monoliths, and let old and new coexist during migration. Most teams see value within 2-3 sprints without disrupting ongoing work.
In PFC #1, PFC #2, and PFC #3, we covered the theory and architecture. Now comes the hard part: actually doing it.
You’ve got an existing codebase. Maybe 50 routes. Maybe 200 components. Maybe years of “temporary” solutions. The question isn’t “Should we adopt PFC?” It’s “How do we adopt PFC without stopping all feature development for six months?”
Good news: You don’t rewrite everything. You migrate gradually.
Series Context: This is Part 4 of the Page-Feature Composition series. This post assumes you understand PFC principles from parts 1-3. We’re focused purely on practical migration.
The Core Philosophy: Divide and Conquer
PFC is built on the ancient principle of “divide and conquer.”
The idea is simple: break complex problems into smaller, manageable, reusable pieces. Instead of one 2000-line component trying to do everything, you have focused features that each do one thing well.
The beauty? Once you divide correctly:
Features become truly reusable across different pages
Teams can work in parallel without conflicts
Testing becomes straightforward
Bugs have clear boundaries
The trick is knowing how to divide. What makes a good feature boundary?
How Big Should a Feature Be?
This is the million-dollar question. The answer: start small and logically encapsulate one part of your app.
The Golden Rule
Too small is always better than too big.
Seriously. A feature that’s too small can easily be merged with another later. A feature that’s too large becomes a mini-monolith, defeating the whole purpose of PFC.
When in doubt, go smaller. You can always combine features later if you realize they’re too granular. You can’t easily split a massive feature that’s already coupled to everything.
What Makes a Good Feature?
Think of features as logical business capabilities that can stand alone.
Good feature boundaries:
User Management - Handles all aspects of user management, including listing, creating, editing, deleting, and searching. Includes all user-related forms and displays. Can answer “Show me users” in any context.
Product Catalog - Handles products: browse, filter, create, edit, and details. Includes product forms, cards, grids, and filters. Can answer “Show me products” in any context.
Order Processing - Handles orders, including creation, viewing, updating status, and tracking. Includes order forms, order lists, and status displays. Can answer “Show me orders” in any context.
Each one is complete and independent. You could develop the user-management feature without the products existing. You could test it in isolation. You could use it on five different pages.
Start Small, Grow Organically
Don’t try to build the perfect feature structure on day one. Start with the smallest logical unit.
Example: Building a shopping app
Don’t start with one giant “e-commerce” feature handling products, cart, checkout, orders, and payments.
Start with small, focused features:
Product catalog feature (just products)
Then add the shopping cart feature (just the cart)
Then add checkout feature (just checkout flow)
Then add the order history feature (just past orders)
Four focused features consistently outperform one massive feature.
The “Elevator Pitch” Test
Can you describe what the feature does in one sentence without using “and”?
✅ Good: “User management handles all user CRUD operations”
❌ Too big: “E-commerce handles products, shopping cart, checkout, orders, and payments”
If you need “and”, you probably have multiple features hiding in there.
The “Can It Live Alone?” Test
Before creating a feature, ask:
Can I develop this without other features existing? Yes → Good feature
Can I test this in complete isolation? Yes → Good feature
Can I use this on multiple different pages? Yes → Good feature
Does it handle ONE clear business domain? Yes → Good feature
If you pass all four tests, you’ve found a reasonable feature boundary.
Practical Sizing Advice
Start here:
3-7 components per feature
1-2 containers per feature
Focused on one business domain
Takes 1-2 sprints to build the initial version
If you’re not sure, err on the side of caution and choose the smaller option. You can always combine later.
Remember: Too small beats too big, every time.
The Mindset Shift
Stop thinking: “We need to refactor the whole app.”
Start thinking: “What’s the smallest valuable change we can make?”
Migration isn’t a project. It’s a practice. You don’t set aside three months to “do PFC.” You adopt it incrementally, one feature at a time, while continuing to ship new features.
The Strangler Pattern
Named after strangler fig trees that gradually replace their host trees.
The idea:
Build new features using PFC
Extract pieces from old monoliths when you touch them
Let old and new coexist peacefully
Over time, PFC becomes the majority
No significant bang rewrite. No project plan. Just better decisions going forward.
Migration Strategies
Select the option that best suits your situation.
Strategy 1: Start with New Features
Best for: Teams actively building new features
When you need to build something new, build it the PFC way. Create the features and pages folders alongside your existing code. New features go into the new structure. Old code stays where it is.
Timeline: Immediate. No refactoring needed.
Strategy 2: Extract One Messy Page
Best for: Teams with one particularly problematic page
Pick your messiest page component and identify the distinct business concerns it handles. Create a small, focused feature for each concern. Move the logic out of the page into features. Update the page to simply compose those features.
Timeline: 1-2 sprints for one page.
Strategy 3: Refactor When You Touch It
Best for: Teams with stable codebases, infrequent changes
The “Boy Scout Rule”: Leave code better than you found it. When fixing a bug or adding a feature, extract just the part you’re changing into a small feature. Leave the rest as-is. Over time, the page gets cleaner.
Timeline: No dedicated time. Happens during normal work.
Deconstructing the Kitchen Sink
Let’s walk through extracting features from a monolithic component, one at a time.
The Problem: Kitchen Sink Component
Imagine your admin page: 500+ lines handling users, products, and orders. Everything mixed together. Services injected for all three domains. The state is scattered everywhere. Methods for every possible action.
It’s unmaintainable. Let’s fix it gradually.
Step 1: Extract First Feature (Users)
Sprint 1: Focus only on users.
Create the user-management feature. Move all user-related logic from the page into the feature’s container. The container handles loading, users, searching, selecting, and deleting. All the user complexity now resides in the feature.
Update your page: replace all the inline user code with the user-management container component. Your page places the container in the template.
Result: Your page component shrinks from 500 lines to ~350 lines. All user services and states have been removed from the page. Progress!
Step 2: Extract Second Feature (Products)
Sprint 2: Now tackle products.
Create the product-catalog feature. Move all product-related logic into this feature’s container. Product loading, filtering, and editing - all moves to the feature.
Update your page again: replace the inline product code with the product-catalog container.
Result: Page is now ~200 lines. Two features extracted. Getting cleaner!
Step 3: Extract Third Feature (Orders)
Sprint 3: Finally, orders.
Create the order-processing feature. Move all order logic into the feature’s container.
Update the page one last time: replace the inline order code with the order-processing container.
Result: Page is now ~50 lines. Almost nothing is left but orchestration.
Step 4: Migrate to Clean Page Component
Sprint 4: Final cleanup.
Move the remaining orchestration logic to a new, clean page component in your pages/ folder. The page just composes the three feature containers and handles navigation.
When a user is selected, the page navigates to the user detail route. When a product is clicked, route to product details. That’s all the page does.
Final result: Went from 500+ lines to ~30 lines. Three focused features. Clean orchestration. Pure composition.
Passing Context to Features
Pages extract context from routes (like IDs) and pass them as inputs to features. The feature receives the ID, loads the data, and displays it. Clean separation.
Example: The user detail page retrieves the user ID from the route, passes it to the user-management container in “detail” mode with the ID. The feature handles the rest.
Handling Legacy Dependencies
Bridging Old Services
Your new features may need to utilize existing centralized services temporarily. That’s okay. Wrap the old service in your feature’s new service. Delegate to it temporarily. Replace the implementation later when you’re ready. No rush.
Shared State
Old and new code might need to share data during migration. Let them temporarily share the feature’s store. Document it as a TODO. Fix it when you can. Keep moving forward.
Team Workflow
Clear Ownership: Small features mean one developer or pair owns each feature. Parallel development becomes natural.
Code Review Focus: Verify that new features are concise, focused, and adhere to the PFC structure. For legacy code, encourage extraction but don’t demand perfection. Praise progress.
Documentation: Create a simple one-page guide showing the feature template and basic rules. Keep it minimal.
Common Challenges
“How do we decide feature boundaries?”
Start small. Use the “Elevator Pitch” test. One sentence, no “and”. If it passes and can live alone, it’s good.
“What if we make features too small?”
Better than too big! You can combine later. You can’t easily split a massive feature.
“Our codebase is too big.”
Doesn’t matter. Start with one small feature. Prove value. Expand organically.
“Team doesn’t know PFC”
Share the posts. Extract one feature together. Learn by doing. Start small.
“What about existing tests?”
Keep them. They still test old code. Write new tests for new features. Delete old tests when old code is removed. Gradual transition.
When NOT to Migrate
Skip if: App sunsetting soon, tiny, overwhelmed team, already well-organized, pure maintenance mode.
Delay if: Major deadline approaching, team crisis
Timing matters.
Summary
Migration is a divide-and-conquer approach through small, focused features extracted one at a time.
Key takeaways:
✅ Start small - too small beats too big
✅ Extract one feature per sprint from monoliths
✅ Let old and new coexist during migration
✅ Move to clean page component when all features extracted
✅ Pass context (IDs, params) as inputs to features
✅ Use “Elevator Pitch” and “Can It Live Alone?” tests
✅ First feature is hardest; gets easier
Start tomorrow. Extract one small feature from your messiest page. Show value. Repeat.
What’s Next
PFC #5 - Real-World Examples
Complete implementation walkthrough. Dashboard with multiple features working together.
Your Migration Plan
Which strategy? (New features / Extract page / Refactor on touch)
What’s your messiest page? (Start there!)
What’s the first small feature to extract? (Users? Products? Orders?)
Does it pass the tests? (Elevator pitch / Can it live alone)
Share in the comments. Let’s learn together.
Part 4 of the Page-Feature Composition series. Follow along at better-frontend.dev for practical Angular architecture.
Previous: PFC #3 - Building Features
Next: PFC #5 - Real-World Examples
This concludes the Page-Feature Composition series. Follow better-frontend.dev for more practical Angular architecture insights.
The Series:
- PFC #1 - The Philosophy
- PFC #2 - Pages as Orchestrators
- PFC #3 - Building Features
- PFC #4 - Migration Strategies ← You are here
- PFC #5 - Real-World Examples

