Introducing the NX Tools to an Enterprise Angular Environment
What are the NX Tools you might ask. A few years ago some of the developers working on Angular left Google and created their own company called Nrwl. They created a build framework called NX which gives you a lot of advantages over common Angular CLI generated repositories. NX also works with other Frameworks like React.
The advantages
- Monorepo functionality
- Have multiple applications in the same directory structure
- Allows you to reuse and share code through libraries
- Have a single set of dependencies
- Modern tooling
- The current Angular CLI uses the deprecated TSLint whereas NX is nicely integrated with it’s recommended replacement ESLint
- The current Angular CLI uses the outdated Karma test runner whereas NX is nicely integrated with a popular alternative called Jest
- The current Angular CLI uses Protractor whereas NX relies on Cypress which offers an awesome End-to-End testing solution
- A modern CLI
- Only actually lint, test and build what is affected by your code changes
- Cache command results and only run commands if the code changed since the last run
NX is modern Angular - The Angular team has their reasons why they haven’t upgraded from TSLint to ESLint yet. But that migration has been discussed for over two years now. If you create a new Angular project, you should use NX in my opinion. Even if you don’t plan to have multiple applications. The other advantages are too big to miss out on.
How We introduced NX
We had 4 Angular applications with different dependencies. One Application was on Angular 11, the other three were on Angular 9. These applications had a good amount of duplicated code. For example for the whole Application Frame (Navbar including all sorts of logic in it).
Luckily, the Applications already had Jest integration thanks to custom code.
So the Application landscape qualifies perfectly for introducing a NX Monorepo:
- Generate a Monorepo and move all applications into it
- Deduplicate the code by introducing reusable libraries
- Integrate modern Tooling like ESLint
And that’s exactly what we did. We started with a small application that was on Angular 9. We generated the Monorepo with npx create-nx-workspace --preset=angular
, created an Angular app inside that monorepo with nx generate app my-angular-app
and then replaced the src
directory of that app with the src directory of the App to migrate.
When I tried to serve that app, it obviously showed me some errors that were a result of migrating from Angular 9 to 11 but following the errors quickly resulted in the application being served correctly.
The next thing I did was to identifiy duplicated code. There was a lof of code that most likely can be reused or at least was not app specific. A few examples:
- Utility code like pure functions
- Autogenerated Clients to communicate with various APIs
- Some components e.g. for displaying Errors
- The whole code for loading and rendering the application frame including profile information and menus
The next applications
The next application was an Angular 9 application aswell. Same process but instead of extracting code into libs, I replaced duplicated code by reusing the libraries. I had to be a little careful because initially when these projects were created, shared code was just copied but over time some minor things got added to the shared code that made it application specific. I compared a lot of files to make sure I don’t miss something.
The third application was an Angular 11 application but the migration mostly wen’t the exact same way. These three applications were minor ones. But the fourth one was the biggest of them all and it is highly production critical. Also it was an Angular 9 application. So we used a different approach here:
- First of all, we left the app in it’s old directory and migrated to the exact same Angular 11 version we use in the monorepo
- Then we tested thoroughly and made sure we didn’t break anything
- Afterwards, we migrated the application to the monorepo but did not yet use the libraries
- We made sure that nothing broke while migrating that application. Obviously we had to adjust CI Pipelines
- The last thing we did was to deduplicate code in that application by reusing the libraries
This was a very safe approach to make sure we did not break anything and if we did so, we could clearly identify which of the steps broke that thing so we could quickly locate the source of the problem. Surprisingly everything went pretty good.
The result
Finally, how does the NX Monorepo look now? We have 4 applications and 6 libraries in it. We make use of most of NX’s advantages:
- All the frontend code in one repository which makes it easy to switch projects, compare code, adjust libraries and so on
- We have no more or at least very little duplicated code thanks to extracting shared code into libraries
- All Angular applications are on the same version. Once we upgrade from Angular 11 to Angular 12 we only need to do the upgrade once and no application gets left behind
- We introduced ESLint in order to make sure to have a consistent code style throughout the codebase. We needed to exclude a few autogenerated files like API clients. Using the
--fix
argument when running the lint command enabled us to offload the migration to a consistent codestyle to an automated process. - When we change code in a library that is used by some applications, we can now run commands like lint, test and build only on the applications that are affected by these changes by running
npx nx affected --target=build
The whole migration took a few weeks of work but I’m sure the time spent will redeem itself over the period of a few months.