You open up your computer, spin up a command line, and type
ng new MyNewAwesomeProject,
cd into the new project directory, run
ng s and start a journey with a brand new Angular project, with the latest dependencies and stuff. Feels awesome, right? Well, sadly, this is not something we would have to deal much in our day to day life of Angular developers, as much as we would like it. In reality, we would most likely have to deal with a project that has been created and maintained by other developers. And there are times that those legacy projects aren't developed in adherence to the best practices of the industry. It is very important to try our best to refactor a project like that to a degree that will make it easier to deal with it.
Before You Start
If you see a project ridden with problems and bad practices, it might be tempting to start refactoring right away. But it is important to clear up several issues before getting to work:
- Make sure management and/or the client actually agrees on the need to refactor. Sometimes, especially on outsourced projects, the clients are just content with how the things are, and are unwilling to pay for more time spent on something the benefits of which they cannot visually comprehend. So it is important to clarify any such bold move with management.
- Make sure your changes won't break the entire codebase. Understand the exact scope of what you want to change and can change, the stuff that you want to change but do not have the time to do, and the stuff that is so messed up that you don't want to touch it for the time being.
- Create a separate git branch and work on, so you don't disrupt the usual workflow. Pull from your main development branch regularly to reduce the chances of painful code conflicts. We've gotta change lots of code!
- Divide your process into clearly defined steps. This is what this article will mainly be about
- Some steps will be way easier to implement and bring way more benefit in the form of performance, bundle size and code quality. Start with steps that are easier, and among them choose those that bring the most immediate improvements.
So, we have made sure our changes are welcome, and we have enough time to work on it. Let's get to work!
Add lazy loading
Lazy loading is a feature that is talked about a lot, but lots of projects out there don't have it implemented. If your project does not have it, it's relatively easy to implement, and will bring large improvements immediately.
- Improvement in performance
- Improvement in bundle size
- Architecture may improve
- Sometimes we would need to move some services up and down the provider tree. But in most cases this would be painless.
Update Angular to the latest version
Some projects that are not maintained properly might be using an older, or even completely outdated version of Angular. In this case, it is important to consider upgrading to a newer version.
Note: be careful when upgrading from version prior to 9 to 9 or higher - introducing Ivy may also introduce bugs and problematic behavior
Important things to consider:
- I advise you upgrade version one by one, say, if you want to go from 7 to 10, go from 7 to 8, then from 8 to 9, then from 9 to ten. This way you might dodge some problems
- Always use instructions from https://update.angular.io/
- Do this before changing other parts of the codebase, because some changes may become obsolete after a major update.
- Always better to have a current version of software
- New features
- Better performance and security
- Possibly smaller bundle sizes
- Some parts of your app might break and need manual fixing
- Upgrade usually takes longer than anticipated
- Some of your dependencies might not work well with updated versions of Angular
Fixing the folder structure
There are several different antipatterns when structuring Angular apps, and most of the applications fall for at least one of those. Now fixing the folder structure might feel like a tedious task, but it actually is almost risk free - as long as we are not significantly altering the code itself, we are pretty much safe, and when changing the folder structure, only the imports section of our code will be affected - and that is usually handled graciously by IDE-s. When changing the folder structure, be careful to follow these rules:
- Group by feature rather than type
- Move relevant services/pipes/directives next to each other
- Store one entity (class, component, enum, interface, etc) in a single file
- Follow rules from the official Angular styleguide
After doing this, consider renaming files and folders. Mention both the type and the feature of the file, for example "bookmarks.service.ts", rather than "bookmarks.ts" or "service.ts", even if it is stored under a folder called either services or bookmarks.
After this, files and folders in our apps will become significantly easier to find.
- Better structured codebase
- Easier to locate files and folders
- Easier for new developers to get to know the project
- Might take a while
Reshape the module structure
Let's say we fixed our folder structure (or even better - had a nice one from the get go), but our architecture is still messy. There is a number of things we can do
- If there are many dependencies registered inside
AppModule, consider moving them
- Move most core dependencies to a dedicated
CoreModule(like translation modules, initialization of core store features)
- Move most commonly used entities across the app (directives or pipes used everywhere) to a dedicated
- Other feature modules also might have their own
SharedModule-s, so be careful to implement those carefully.
- Use this part of the styleguide for better reference
- Hugely improved application structure
- Even easier to explain the project structure to other developers
- Will definitely take lots of time to implement, so start with caution
- Will probably result in some bugs, so prepare for rigorous testing
As mentioned, this is a step that will take a lot of time, so plan ahead carefully before starting; you don't want starting all over again when one design proves unsuccessful!
Start using the linter
Linting is a very important stage in our development cycle, as it allows us to remove common antipatterns and code smells early on.
Start by simply running
first. It will give you the list of linting errors your application has. This will allow you to understand how much work will probably be needed to fix the apps. We can then run
ng lint --fix
to make the linter fix those linting errors it can. This will not fix all the errors, some will require a bit of manual work. Go on and fix the other errors, which should not be very hard at this stage.
- Instant improvement to code quality
- Sometimes you might disagree with the linter. This might be the time to read about linter rules.
Improve your Observables
So, we have moved the folders around, changed the module structure, and now our app has a better outward look. Let's get to changing the actual code of our app. And the best place to do that is to change the
Observables we have.
Lots of projects suffer from abusing RxJS. Good news is, fixing problems with RxJS in most cases is very straightforward, and, if we are careful enough, almost risk free. Here are 3 essential steps:
- Start using operators. Take a look at your
subscribestatements: chances are, you have lots of logic inside those callbacks that could be expressed better using RxJS operators. Use my first article on RxJS in Angular and RxJS best practices for guidance.
- Then take a look at where you use
subscribestatements to 1) take the next value, 2) assign it to a property on our component and 3) display it in the template. Now try starting to remove those statements and switch to using the
- Now start the most challenging part: start converting imperative logic where it is relatively simple to declarative logic using Observables. Take a look at my second article on RxJS in Angular for more information.
- Relatively easily achieved, especially the first two steps
- Huge improvement to code quality
- As a bonus, sometimes may solve
- Might cause bugs, as any code change
- If you're unfamiliar with RxJS in depth, might require learning before starting
So what's next?
After we have done these mostly straightforward steps, our application must look way better than we found it, but it still can use some improvement. But those other steps will be more specific, so here are some of them:
- Go through individual components and start improving the actual code inside them
- Work on using more functional/reactive/declarative code vs imperative
- Improve your directive/component selectors.
- Use HTTP Interceptors
- Consider using a state management library like NGRX.
We will discuss those steps in more detail in future.
And remember, there are always ways to improve our codebases even more!