Before You Begin
When you see a project full of issues and questionable practices, the urge to dive into refactoring immediately can be strong. However, it's crucial to sort out a few matters first:
- Confirm that management or the client supports the refactoring effort. This is especially important on outsourced projects where stakeholders may be satisfied with the status quo and hesitant to invest in changes whose benefits aren't immediately visible. Get explicit approval before embarking on any significant restructuring.
- Ensure your modifications won't destabilize the entire application. Carefully map out what you intend to change and what you're able to change, what you'd like to improve but lack the time for, and what's so problematic that it's better left untouched for now.
- Work on a dedicated git branch to avoid interfering with routine development. Regularly merge changes from your primary development branch to minimize the likelihood of messy conflicts. There's a lot of code to alter!
- Break your work into distinct, manageable phases. That's exactly what we'll explore in this article.
- Some tasks will be simpler to execute yet deliver outsized gains in performance, bundle size, and code maintainability. Begin with the easier items, and among those, prioritize ones that yield the most noticeable improvements right away.
Assuming your changes have been approved and you've allocated sufficient time, let's roll up our sleeves and get started.
Introduce Lazy Loading
Lazy loading gets plenty of discussion, yet numerous projects still omit it. If your application is one of them, integrating it is fairly straightforward and offers rapid, substantial benefits.
What you gain:
- Enhanced performance
- Reduced bundle size
- Potentially better architecture
Watch out for:
- In some cases, you'll need to reposition services within the provider hierarchy. Generally, though, this won't cause much trouble.
Upgrade Angular to the Latest Release
Applications that haven't received proper upkeep might rely on an older, or perhaps entirely outdated, Angular version. In such situations, upgrading to a more recent release should be seriously considered.
Caution: exercise care when moving from versions before 9 to 9 or newer — enabling Ivy could introduce unexpected bugs or problematic behavior
Key considerations:
- Upgrade incrementally; for instance, if you're moving from 7 to 10, go through 8 and 9 sequentially. This step-by-step approach may help you avoid certain issues
- Consult https://update.angular.io/ for official guidance
- Handle this before touching other parts of the codebase, since modifications made earlier could become irrelevant following a major release.
What you gain:
- Being on a current software version is invariably advantageous
- Access to the latest features
- Improved performance and security
- Often, smaller bundle sizes
Watch out for:
- Certain parts of your application might malfunction and require manual intervention
- The upgrade process frequently takes longer than initially expected
- Some third-party dependencies may not be compatible with the updated Angular version
Reorganize the Folder Structure
There are several common anti-patterns in Angular project organization, and most applications exhibit at least one of them. While restructuring folders might seem like a tedious chore, it's essentially risk-free — as long as the underlying code remains unaltered, you're on solid ground. Folder reorganization primarily impacts import statements, and IDEs usually handle those adjustments seamlessly. When undertaking this task, adhere to these guidelines:
- Organize around features rather than file types
- Keep associated services/pipes/directives in close proximity
- House each entity (class, component, enum, interface, etc.) in its own separate file
- Follow the official Angular styleguide
Once the structure is in place, think about renaming files and directories. Include both the type and the feature in the name, like "bookmarks.service.ts" instead of "bookmarks.ts" or "service.ts" — even if the file already resides within a folder named services or bookmarks.
With this approach, locating files and folders within the project becomes significantly simpler.
What you gain:
- A more logically arranged codebase
- Simpler navigation to specific files and directories
- Quicker onboarding for developers new to the project
Watch out for:
- The process can consume a fair amount of time
Restructure the Modules
Suppose your folder layout is now in good shape — perhaps it was solid from the beginning — yet the overall architecture still feels chaotic. There are several actions you can take:
- If
AppModulehas too many registered dependencies, relocate some of them - Transfer critical core dependencies, such as translation modules and core store initialization, into a dedicated
CoreModule - Place commonly used entities, like directives or pipes utilized across the application, into a designated
SharedModule - Be mindful that other feature modules may also have their own
SharedModule-s; implement those thoughtfully - Consult this styleguide section for detailed reference
What you gain:
- Markedly cleaner application architecture
- Simpler explanation of the project structure to fellow developers
Watch out for:
- Expect this to take considerable time, so proceed with careful planning
- Bugs will likely surface, so gear up for thorough testing
As noted earlier, this phase demands a significant time investment. Plan carefully upfront; you don't want to backtrack if a particular design approach doesn't work out!
Adopt a Linter
Linting plays a vital role in the development process, enabling the early detection and removal of common anti-patterns and code smells.
Begin by executing
ng lint
to see all the linting issues your project currently has. This gives you a sense of the effort required to address them. Next, run
ng lint –fix
to have the linter automatically correct the issues it can handle. Be aware that this won't resolve everything; some problems need hands-on fixes. Tackle the remaining issues manually — at this stage, they typically won't be overly complex.
What you gain:
- Immediate boost in code quality
Watch out for:
- There may be instances where you disagree with the linter's recommendations. If so, it's worth delving into linter rules.
Refine Your Observables
With folders and modules reorganized, your application presents a much improved exterior. Now it's time to dig into the actual code, and the most effective starting point is the Observables in use.
Many projects fall into the trap of misusing RxJS. The encouraging news is that rectifying these issues is typically uncomplicated and, with adequate caution, virtually risk-free. Here are three fundamental steps:
- Embrace operators. Examine your
subscribeblocks — you're likely to find a lot of callback logic that could be more elegantly handled with RxJS operators. My initial article on RxJS in Angular and the piece on RxJS best practices offer useful guidance. - Next, spot where
subscribeis used to 1) receive the next value, 2) assign it to a component property, and 3) render it in the template. Work on eliminating these patterns and adopting theasyncpipe instead. - Then comes the trickier part: shift straightforward imperative logic toward declarative, Observable-based approaches. My second article on RxJS in Angular provides more depth.
What you gain:
- These are relatively easy wins, particularly the first two steps
- Significant improvement in code quality
- Along the way, you might even resolve
ExpressionChangedAfterItHasBeenCheckederrors
Watch out for:
- Bugs can arise, as with any code modification
- If RxJS isn't second nature, you may need to invest time learning before diving in
What Comes Next?
After completing these mostly straightforward steps, your application should look considerably healthier than when you started. Still, there's always room for improvement. The remaining steps are more specialized, but here's a taste:
- Inspect individual components and enhance the logic within them
- Prioritize functional/reactive/declarative coding over imperative approaches
- Polish your directive and component selectors
- Make use of HTTP Interceptors
- Evaluate incorporating a state management library such as NGRX
We'll delve into these topics in more detail later on.
Keep in mind, there's always potential to refine our codebases further!
