asim.dev

When you can't use the word “BIG BANG”

ng-conf 2017 · Salt Lake City, USA

by Asim Hussain · 31 March 2017

talk tech
When you can't use the word “BIG BANG”
▶ Watch on YouTube ↗

This is an ng-conf workshop on the technique I used with my larger enterprise clients to migrate a real AngularJS app to Angular (then version 4) without ever going dark. These are the people who literally can’t use the word “big bang” — production traffic, regulatory pressure, no appetite for a three-month rewrite — so the whole talk is about staying releasable at every single step.

AI-generated summary of my talk

Jump into the talk

  1. 0:00 Why "big bang" is off the table
  2. 3:02 How the workshop is structured
  3. 8:08 Terminology: one vs four, red vs blue
  4. 12:13 The mental models: dual boot, chameleon, infection
  5. 19:20 Asim's 10-step plan
  6. 23:21 Componentification
  7. 26:27 ES6-ifying your services
  8. 31:29 Dual booting both frameworks
  9. 34:33 Migrating services and third-party modules
  10. 42:39 Components, routing and the "tiny pop"

Why you can’t use the word “big bang”

Anyone who’s done a big transformative refactor knows it’s always a bit more complex and risky than you first thought. The technique I teach here is for my larger clients — the ones with a production application, lots of traffic, often regulatory issues — who simply can’t afford the downtime. They can’t bump a version number, watch everything break for three months, fix it all, and only then ship. So everything here is iterative and step-by-step, and the headline rule is that at every step you have a releasable application you can throw out into the wild.

One bit of bad news up front: Angular 4 (and 2) only works as a single-page app. So if your AngularJS app is still a multi-page app, you have to convert it to an SPA before you even begin. I won’t pretend this is clean — it’s messy work, there’s no magic repo you point at your source folder. But underneath the mess there’s a really simple set of rules, and if you hold onto those you always know where you are and you can find a path across to the other side.

The mental models

Before the plan, I teach the mental models, because if those click everything else follows. I run them as a little picture quiz.

Dual boot. This is the foundation: you include both AngularJS and Angular 4 libraries in the application at once. Your final bundle literally contains both frameworks during the transition, so it’s bigger — that’s just something you live with for a while. The payoff is that you can convert entities to Angular 4 and have them live next to your AngularJS entities. We also call this hybrid mode.

Chameleon. It turns out side-by-side isn’t quite free. To make an AngularJS entity usable in Angular 4 you upgrade it; to make an Angular 4 entity usable in AngularJS you downgrade it. In my diagrams an up arrow is upgrade and a down arrow is downgrade. And because both halves share a single dependency-injection instance, they’re sharing the same instance of the data — if an Angular 4 component changes something on a service, the AngularJS side sees the same change.

Control freak and the infection. Every DOM element in the tree is controlled by either AngularJS or Angular 4, and the topmost component is always AngularJS. That constraint is why you spend the migration upgrading and downgrading things at different levels just to get everything to see everything else. The analogy I love giving clients is disease: you infect the AngularJS app with Angular 4 at the leaves, and it spreads upward until it takes over the whole thing — then you drop AngularJS and you’re done. (One nice side effect of hybrid mode and Angular’s zones: you no longer need scope.apply, even in your AngularJS code.)

Asim’s 10-step plan

The plan is deliberately generic so you can lift it straight into your own projects, and I demo each step against a simple contacts app — load, search, sort, delete, edit, create.

  1. Adopt a style guide, and the one non-negotiable: one entity per file (a component, a service, a filter — I call any of them an “entity”). You need that for module loading later.
  2. Move to TypeScript and build tools — I use webpack from the start.
  3. Upgrade to at least AngularJS 1.5, because that’s where AngularJS components arrive, and components map cleanly onto Angular 4 components.
  4. Componentify — convert controllers to 1.5 components, starting with the ones in your top-level router config.
  5. ES6-ify your services — convert them to classes, swap $resource for $http, drop $q for native promises, use services over factories (a class is just a constructor function, so it slots straight in). Honestly the most fun part.
  6. Dual boot — add Angular 4 and get it bootstrapping in hybrid mode.
  7. Migrate the services to Angular 4, leaf-first.
  8. Migrate the components to Angular 4.
  9. Migrate routing — leave it as late as possible.
  10. Strip every reference to AngularJS — webpack will happily bundle a stray import, so hunt them all down.

You could genuinely stop after step 5: a TypeScript, component-based, ES6 AngularJS app following a style guide is a perfectly nice place to be.

The grind: services, components and the messy bit

Steps 6 onward are what I call the grind — one service, one component at a time. Dual booting itself is mostly copy-and-paste: you create a basic NgModule, override its ngDoBootstrap to do nothing, grab the UpgradeModule and bootstrap both frameworks together. Remove ng-app from your index.html so AngularJS doesn’t accidentally bootstrap itself. That bootstrap code never changes between projects.

Migrating a service that’s already an ES6 class is almost trivial — you decorate it with @Injectable, provide it in the app module, then call downgradeInjectable so AngularJS can still use it. That’s three lines, and it’s exactly why converting to classes early pays off.

The genuinely hard part — the bit where every head in the room nods — is third-party modules you don’t control. There are only three solutions I know of: rewrite it, find an existing Angular port, or upgrade it temporarily. The first two are permanent; the third is a crutch, because anything you merely upgrade still depends on AngularJS, so you’ll have to replace it before you can finally rip AngularJS out. Rewriting is less scary than it sounds — with zones you no longer need framework-specific wrappers, so you can often just use a plain library directly. I rewrote a spinner from the underlying library in a handful of lines.

The tiny pop

Once the components and services are across, all that’s left is routing — and I deliberately save it for last because by then it’s easy. Some people advocate migrating one route at a time; I don’t bother. You drop hybrid mode, bootstrap with Angular 4, add a tiny root component, switch from UI-Router to the Angular router, and clean up everything you’d only upgraded as a crutch (the UI-Router services just get dropped; the toaster I’d upgraded got swapped for a real Angular port).

So even the final cutover isn’t a big bang. I call it a tiny pop. You end with a clean Angular 4 application, AngularJS gone, and — crucially — you were releasable the entire way there.