This story starts in the year 2012 where I started writing my first Angular1 app. It started off great, amazing 2-way binding forms with a strong love-hate relationship with
When Angular2 was born last year, Angular1 quickly became the red-headed step child (sorry red heads). The more I used it the more I was in love. At work, we really try to stay on top of latest tech stacks, it can be painful sometimes but if you don’t the code will deprecate pretty quickly and next thing you know there is no upgrade path. We also had that awkward React + Angular1 mix for some of the data viz we do that you really need some sort of on-push change detection for rendering SVGs.
I had this dilemma; I had 50k LOC of Angular1 in my work app. I’m very fortunate that my company embraces new technology but a total re-write was gonna be a tough sell to even the most adventurous businesses.
I asked my Twitter audience ( you should totally follow me if you don’t ) and I was a little shocked with the results:
When I was a kid, I played The Oregon Trail avidly and that’s how I kinda felt, 2,000 miles between me and Angular2. So thus the problem, how do I get from Angular1 to Angular2? Before I get started, I’m also a host on AngularAir where we recently had a guest talking about the path to upgrade, its a great show I highly recommend it. I’m going to run over a few of the points we discussed along with some code.
I believe there is 4 different approaches to get to Oregon, I mean Angular2. Lets take a quick look at pros and cons of them and then I’ll share the path I took…
A rewrite is certainty one option to upgrade, its got some great pros like:
- Clean new code
- One big push vs several small ones
- No awkward Angular1 code mixed with Angular2
- No download penalty of Angular1 and Angular2
- Fresh start to apply lessons learned
- Remove the hacks from old code and backlog items
- Team morale of working on all new stuff
- Easier hiring of just 1 framework vs legacy knowledge required
- Awesome performance from AoT
That’s a lot of perks but it has some serious considerations you have to be able to swallow:
- High effort
- High risk for regression
- High chance of failure from getting distracted on bugs or it taking too long
- Rewrite all your tests for all application features
Large organizations can probably swallow some of that risk and effort with R&D teams but for most of us its a tough sell but don’t fear there are other ways!
You could incrementally upgrade each page without using
ng-upgrade by actually creating separate pages. In a incremental approach, any new features being built with medium effort or higher written in Angular2. The advantages are not too shabby either:
- Less risk than total rewrite
- Easier to separate Angular1 code from Angular2
- No bloat of both frameworks being downloaded over the wire
- Only have to write new tests for new features
- Good performance from Angular2 and AoT-able
but it does have quite a few downsides like:
- Awkward build processes
- Have to maintain Angular1 code and Angular2 code at same time
- Consistent look and feel gets tough to maintain between pages
- Hiring has to consider Angular1 previous experience
- And no one wants to be the guy/gal working on the legacy stuff haha
Write Angular1 code like Angular2
I’m not too fond of this idea so I’m not going to spend a lot of time on it but it is a option so I feel obligated to mention it. Rather than use Angular2 at all, you could just write your Angular1 code in a way that prepares it for a Angular2 migration sometime in the future. There is a project out there called ng-metadata that even lets you simulate all the same syntax.
The thought process here is I will eventually have all my ng1 code in ng2 format and one day I’ll be able to magically pull the rug out from under it and it just all work in ng2. To be honest, I don’t ever see that happening with all the differences between the 2 frameworks so I’m just going to stop at that.
When Angular2 was first announced in 2014, it scared us all. A radically different syntax with no path for the Angular1 folks. They included a tool called
ng-upgrade in the framework that would allow you to run Angular1 and Angular2 in parallel so you could write new Angular while you maintained your legacy code.
The concept is relatively straight forward, its a Angular1 directive that wraps Angular2 components. It transposes the inputs of the directive to the inputs of the Angular2 component. When Angular1 scope digest is triggered, we simply pass those changes onto the Angular2 component [reference]. It's not even a new concept, we’ve seen this done with React and various other frameworks to interop with Angular1.
Sounds like a magic bullet right? Not so fast…but before we get into the gotchas, it does have some perks:
- Low risk with an incremental approach
- Maintain all your existing tests for current code while building new ones for the new code ( BTW protractor does work in hybrid )
- Able to maintain consistent look and feel between both frameworks
- Re-use Angular1 components in portions of the Angular2 and vice versa
the downsides at first glance aren’t bad:
- Bloat of downloading both frameworks across the wire
- Hiring has to consider Angular1 previous experience
- No one wants to be the guy/gal working on the legacy stuff haha
but then when you a bit deeper, you find a bit more…
- Not all Angular1 components work in ng-upgrade and vice versa
- It can be a nightmare to get it all playing nicely, especially for very complex builds and routing
- Good luck with AoT, its difficult enough in pure ng2
- You probably want to upgrade your code to TypeScript ( but don’t have to )
What’d you do?!
Naively you don’t think about all that ^^ until you get knee deep in it. I was naive and used ng-upgrade though so here’s what happened next:
- I quickly realized that hardly any of my components would work in ng-upgrade, basically anything that isn’t using Component in ng1 doesn’t work ( which is a not a lot of them ) so the whole thought process I can share components went out the window.
- I was using ui-router which sounds like a nightmare at first but luckily @ChrisThielen work on ui-router allows you to run hybrid VERY easily! This literally would not be possible without that so hugeeee shout out!
- I was doing lazy-loading in ng1, for anyone that knows about that its a tough cookie as it is and now combine that with ng2 components in upgrade and it gets even tougher. I was able to get it going though so I’m lazy-loading both ng1 and ng2 now which is pretty awesome!
- Data flow between ng1 and ng2 frameworks is radically different, you really have to throw many of your ng1 concepts out the window. It gets tough to maintain both mental models.
- While protractor does work, its got some hacking you gotta do to get it to work on hybrid applications.
- I’ve been using Babel since 2014, I really didn’t want to bite the bullet to upgrade it all to TypeScript so I had to hack Babel to work with TypeScript style code.
- Some Angular2 components need extra hackery to work right when downgraded
But at the end of the day, I did get it working! As proof I created ng1-ng2-webpack-lazy-uirouter project on Github. In the project, I interop Angular1 and Angular2 inside each other and even in nested routes!
We used that boiler plate as the starting ground and have been incrementally updating features as we add new features. We are about 25% or so through it and have had good results so far.
So piece of cake right? Haha, with all that said to be honest in hindsight which approach would I have taken? Probably the same one, once you get over the hump its really not that bad and most people don’t have as fancy setup with lazy-loading as me so it won’t be as tough…but then again if your code isn’t that fancy maybe you should just rewrite?