Part 4 of the Angular in Production series
One thing I've noticed after working on larger Angular applications is that components rarely becomes difficult to maintain overnight, it hapens gradually. When you add a feature and another, and new modals, new API calls and new permission checks. Nobody deliberately decides to create a thousand-line component. It simply happens because adding a little more logic always feels easier than creating another abstraction. At first, it doesn't seem like a problem and the application still works. But eventually you open the file and realize you're no longer looking at a UI component. You're looking at an entire feature packend into a single class. That's usually the point where every new change starts taking longer than it should.
Component Size Is a Symptom, Not the Problem
One mistake I used to make was measuring components by their number of lines. A component with 800 lines isn't automatically bad. Likewise, a component with 150 lines isn't automatically good. What matters is responsibility. I've seen relatively small components responsible for:
- loading data
- transforming data
- validating forms
- handling permissions
- opening dialogs
- managing application state
- formatting values
- reacting to router changes
- calling multiple APIs
Technically, they weren't very large but architecturally, they were doing the work of several different parts of the application.
Whenever I open a component today, I usually ask myself if the component has a clear responsibility and if the answer isn't obvious, that's normally a sign that the component has started growing in the wrong direction.
Business Logic Gradually Takes Over
Most oversized components don't start with business logic. It gets added one feature at a time. Something like this feels perfectly reasonable:
loadUsers() {
this.userService.getUsers().subscribe(users=> {
this.users=users;
});
}
A few weeks later, the method has envolved:
loadUsers(){
this.userService.getUsers().subscribe(users=> {
this.users=users.filter(user=> user.active);
this.totalRevenue=users.reduce(
(sum, user) => sum + user.revenue,
0
);
this.canExport=
users.length < this.subscription.maxUsers;
this.chartData = this.buildChart(users);
});
}
Nothing here is necessarily wrong. Every new requirement made sense when it was added. The problem is that the component has quietly stopped being responsible only for presenting users. Now it's filtering data, calculating business metrics, checking subscription limits and preparing chart data.
As the project grows, these methods usually continue expanding. Eventually, changing one piece of logic means understanding everything else happenning around it. Today, whenever I notice business rules accumulating inside a component, I try to move them somewhere that better reflects thei purpose.
Instead of this:
loadUsers() {
this.userService.getUsers().subscribe(users => {
this.users = users.filter(user => user.active);
this.totalRevenue = users.reduce(
(sum, user) => sum+user.revenue,
0
);
});
}
I prefer something closer to:
loadUsers() {
this.dashboardService
.getDashboardData()
.subscribe(data => {
this.user = data.users;
this.totalRevenue = data.totalRevenue;
});
}
The component doesn't become simpler because it has fewer lines. It becomes simpler because it no longer owns business decisions. Its job is simply to display information.
Components Should Coordinate, Not Implement Everything
Over time, I've started thinking about Angular components more like coordinators. Their responsibility is connecting different pieces of the application. Not implementing every piece themselves.
For example, a dashboard component might request data, pass data to child components, react to user interactions and trigger navigation. That's already enough responsibility. It doesn't also need to know, for example, how invoices are calculated, how permissions are evaluated, how reports are generated or how exports are formatted.
Whenever I see methods like these living together inside the same component:
calculateInvoice()
validatePermissions()
buildChartData()
generateStatistics()
formatExport()
sendNotification()
I immediately start asking whether those responsibilities belong somewhere else.
The goal isn't to move code into services just because "that's cleaner". The goal is making every file responsible for solving one kind of problem. That usually makes future changes much easier because you're no longer afraid of breaking unrelated functionality.
A Single Screen Doesn't Mean a Single Component
One misconception I has early on was assuming that every page should correspond to a large component. After all, it's one screen. Why split it? Then those mages started growing. Imagine a dashboard that contains:
├─ User summary
├─ Sales chart
├─ Recent orders
├─ Notifications
├─ Activity feed
├─ Quick actions
└─ Reports
From the user's perspective, that's one page. But from a development perspective, it's several independent features. Each one may eventually require
- its own API requests
- its own loading state
- its own permissions
- its own interaction
- its own tests.
Keeping everything inside a single component simply because it appears on the same screen often creates unnecessary coupling. Splitting features into dedicated components doesn't just reduce file size. It makes ownership much clearer.
Someone working on notifications shouldn't need to understand how reports are generated. Likewise, changing the sales chart shouldn't risk breaking recent orders. As applications grow, those boundaries become increasingly valuable.
Large Templates Usually Hide the Same Problem
Something similar happens in the template. At first, the HTML is easy to follow. Then a few conditions appear. A couple of loops. Some permission checks. Different layouts depending on the user's role. Eventually, you find yourself scrolling hundreds of lines just to understand how a single section of the page is rendered.
A simplified example might look like this:
</app-admin-panel>
<app-basic-panel
*ngIf="dashboardMode === 'basic'">
</app-basic-panel>
</div>
<app-user-panel
*ngIf="!user.permissions.includes('admin')">
</app-user-panel>
</div>
None of these conditions are wrong individually. The issue is that every new requirement adds another branch to the template. Eventually, understanding what the page displays becomes almost as difficult as understanding the component itself. Whenever a section starts having its own logic, I usually stop asking "Can I keep adding conditions here?" and start asking "Should this become its own component?" More often than not, the answer is yes.
Inputs and Outputs Can Reveal Design Problems
Passing data between components is completely normal. That's exactly what Angular components are for. But over time I've noticed something interesting. When a component starts receiving too many inputs, it's often because it's responsible for too much. For example:
@Component({
selector: 'app-dashboard-card'
})
[user]="user"
[permissions]="permissions"
[settings]="settings"
[statistics]="statistics"
[reports]="reports"
[loading]="loading"
[theme]="theme"
[subscription]="subscription"
(refresh)="refresh()"
(delete)="deleteUser()"
(export)="exportDat()">
</app-dashboard-card>
There's no magic number where this becomes wrong. But whenever I find myself passing almost the entire page state into a single child component, I usually pause. Sometimes the child isn't really one component anymore. It's another feature that deserves to be split further. The same applies to outputs. If one child emits five or six different events, it's worth asking whether it's trying to do too many different things.
Forms Tend to Grow Faster Than Everything Else
If there's one place where oversized components appear most often, it's forms. They start small, a few fields, a submit button. Then the business requirements arrive. Conditional fields, dynamic validation, role-based visibility, autosave, arachments, external lookups... Before long, the component responsible for the form also contains most of the application's business rules. I've learned not to fight this. Large forms are naturally complex. What helps is separating responsibilities around them. For example:
- one component renders the personal information section
- another handles addresses
- another manages attachents
- shared validation lives in dedicated services
- reusable controls become standalone components
The form still behaves as a single experience for the user. But the codebase becomes much easier to navigate.
The Best Time to Split a Component Is Earlier Than You Think
One lesson that took me a while to learn is that component extraction shouldn't be a last resport. For a long time, I waited until a component became painful before splitting it. Now I try to do it much earlier. Not because I enjoy creating more files. Because smaller, docused components tend to evolve independently. That mean fewer merge conflicts, simpler reviews, clearer ownership and much less fear when changing existing functionality. Ironically, splitting components early often results in writing less code overall. There's less duplication, less branching and fewer places where unrelated logic accidentally becomes connected.
Conclusion
Large Angular components rarely become difficult to maintain because Angular encourages bad practices. They become difficult because features keep accumulating in the same place. Each individual change feels reasonable. Taken together, they slowly transform a UI component into something that's responsible for an entire feature. Today, I don't decide to split a component based on its size. I do it when I notice it solving multiple different problems. That's usually the first sign that future changes are going to become harder than they need to be. Keeping responsibilities small doesn't just improve readability. It also makes testing, debugging and adding new features much less stressful.
Next Article in This Series
Part 5: The Angular Subscription Problems That Only Appear in Production
Previous Articles in This Series
Part 1: Why Angular Applications Get Slower as They Grow
Part 2: Angular Bundle Size Is Only One Part of Performance
Part 3: The Angular Change Detection Mistakes That Make Large Apps Feel Slow
Thanks for reading! If your Angular application has accumulated performance or maintainability problems over time, this is the kind of work I help teams with: debugging existing codebases, improving performance and refactoring incrementally. You have a link to my upWork in my Dev profile.
Top comments (0)