These are exciting times: just a few hours after Angular 2.0 Final release was announced, we are already proudly running it in production!
In this blog, I will describe why we have migrated, and show you how you can too.
Why we migrated?
At this very moment, Exponea’s frontend is a hybrid application, powered by both Angular 1.5.8 and Angular 2.0, together counting 132 custom components in 49 application modules.
The speed of Angular 2 is the main reason why we decided to migrate. Angular 2 is much faster thanks to its conceptual improvements (such as partial tree walking during change detection, generating VM friendly code…), and also improvements that can be done on your side with libraries like Immutable.js or Observables/RxJs.
Another reason is that Angular 2 embraces TypeScript. Why should that be a factor? For one thing, we believe it is the ideal language for writing large applications for the web. For another, we found out in discussions within our development team that TypeScript makes them happy.
As to why we started migrating before the final release, the answer is simple: because the Angular team made it possible. We applauded their bold decision to support the hybrid mode for people migrating from Angular 1 to Angular 2. There was nothing to stop us once the migration was technically possible!
As you can read in what follows, starting the migration was a piece of cake thanks to improvements in both Angular 2 and TypeScript.
How to migrate?
Big-Bang vs. Continuous rewrite
In choosing your approach to migration, remember that two frameworks need to co-exist in a single application. This inevitably means some trade-offs, the biggest of them being a larger download size (at this moment, the download size of Angular 2 dependencies is 169kB gzipped, which is not that bad).
If you want to minimize this interim period, consider preparing for the migration, and then switch as fast as possible. Migrating directives to Angular 1.5 Component API seems to be the most useful thing to do before migrating, and if this is your preferred approach, we encourage you to read blogs such as the one at Thoughtram.
We chose a different strategy – as a dashboard application, Exponea does not need that kind of speed for the first load; performance is crucial mainly inside the application (but saying we are not eagerly looking forward to the lazy-loading and server pre-rendering improvements in Angular 2 would be a lie). We decided to rewrite the application module-by-module instead.
In order to start the migration, you have to install Angular 2 dependencies and finally bootstrap your application via an instance of UpgradeAdapter:
The step-by-step guide in the Angular 2 upgrading guide is thorough and informative. I strongly recommend you read that one if nothing else.
From that point, you can start mixing Angular 1 and Angular 2 components and services as wildly as you wish 😉
But we decided to take a smoother approach.
Vertical vs. Horizontal Rewrite
In theory, many applications are layered similarly to ours:
- At the top-most layer is the router that switches between application views.
- Below the router are application modules (each serves a specific functionality, such as analytics or campaigns).
- Modules register their routes in the router
- Each module consists of one or more views.
- Views are formed by view-specific components that serve a single purpose (such as <trend-viewer>, <report-editor>, <customer-filter>, etc.)
- At the bottom-most layer are low-level components that serve a single purpose each and from which view-specific components (e.g., <calendar>, <select-box> and others) are built
When rewriting application to Angular 2, we started with the bottom layers first, replacing the low-level components with their Angular 2 counterparts, polishing and redesigning when and where necessary.
You can start migrating entire views and modules after you have rewritten enough components.
In the end you can switch to the Angular 2 router, remove Angular 1 as a dependency from your build and start leveraging all benefits of a full Angular 2 application, such as lazy module loading, server pre-rendering.
Rewriting application to TypeScript
tsconfig.json: allowJs: true
(for us the TypeScript compiler discovered only one issue of invalid object definition – duplicated property in the translation file)
- switch file extension from .js to .ts
- use notImplicitAny: false in tsconfig.json
- install typings for libraries you use, such as angular 1, lodash, moment, tether
Compiling in Modular vs. File-by-file manner
For the sake of simplicity, we split the build into two branches:
- go file-by-file to build all the .js and .ts files for Angular 1 components and views (via glob pattern)
- go modular to build the Angular 2 part of the application
This way, we have been able to rewrite the application incrementally, file by file.
Modular build & limiting download size with Rollup.js
In order to minimize the startup overhead which the Angular 2 part of the application can cause, we used a modular build with tree-shaking leveraging Rollup.js:
This will compile the entire Angular 2 part of the application with the root in main.ts, cutting off any redundant modules.
In particular, we have a production profile of Rollup build (see above) and a development profile (which externalizes Angular 2 and other dependencies and significantly speeds up development time). You can read more about this setup in this blog.
This way we were able to get to 169kB of gzipped size – a really small fraction of the entire Exponea application.
Exciting times ahead!
Introducing Angular 2 in the context of our large Angular 1 application has really been a piece of cake, and we could not be happier. We were really surprised that almost no additional download overhead has been required.
All in all, this has been just the beginning of a long journey, but if it bodes anything for the future, it should be an overall enjoyable experience!