asim.dev

The AngularJS Migration F.A.Q.

AngularUP 2017 · Tel Aviv, Israel

by Asim Hussain · 31 May 2017

talk tech
The AngularJS Migration F.A.Q.
▶ Watch on YouTube ↗

I teach a full-day workshop on migrating from AngularJS to Angular, and every single time I give it, people queue up afterwards with the same handful of questions. So instead of walking through the migration path yet again, I built this talk around those frequently asked questions — the three I get asked most — after a quick five-minute recap of how migration actually works.

AI-generated summary of my talk

Jump into the talk

  1. 3:04 Migration basics in five minutes
  2. 4:05 Dual-booting and hybrid mode
  3. 7:16 The three FAQs
  4. 8:16 What do I do with my scope watches?
  5. 11:16 The answer: observables, streams and operators
  6. 17:19 Where are all the third-party Angular modules?
  7. 20:22 Zone.js kills the wrapper
  8. 22:23 What replaces $emit and $broadcast?

Migration in five minutes

Before the questions, a quick recap so everyone’s on the same page — and a bit of terminology, because the versions trip people up. When I say Angular I mean the latest Angular; when I say AngularJS I mean Angular 1. Both logos happen to be red, so for the colour-blind folks in the room: if you see blue on a slide, I’m talking about Angular; red is AngularJS.

The technique that makes migration possible is dual-booting: you run AngularJS and Angular in the same application at the same time, and the app is then said to be in hybrid mode. That lets real Angular entities live side by side with real AngularJS ones. My recommended approach is to take your AngularJS app and “infect” it with Angular. You start at the leaves — say, a resource — rewrite it as Angular, then call downgradeComponent so the new version can still be used from within the old AngularJS code. Then you work up the tree: rewrite a service, downgrade it because the AngularJS components still need it, and keep going until everything is Angular. At that point you drop AngularJS, jump through a couple more hoops, and you’re left with a pure Angular app. There’s far more to it — I teach a ten-step plan — but that’s the shape of it.

What do I do with my scope watches?

When people ask this, they’re rarely asking literally. They’re usually asking: “I’ve got the form in my application — what do I do with it?” I worked at a company years ago where a particular form was a running joke; we’d meet for drinks and inevitably someone would bring up the form. It had a nested hierarchy of controllers leaning on scope inheritance, asynchronous calls setting scope variables on instantiation, and ng-model bindings wired to $scope.$watches that triggered functions that set other variables that triggered other watches. You’d enter the same values in the same order and get different results. My physics is rusty, but I’m fairly sure that’s the definition of chaos theory.

The solution in Angular is observables. To get them you first need the idea of a stream: a sequence of values over time — a series of numbers, mouse coordinates, key presses, or even a JSON representation of an entire form updating as you type. An observable is a blueprint for creating these streams and the operations between them; the broader idea is called reactive programming.

Take a simple add function. Imperatively, if b changes, you have to notice it changed, re-call add, and push the result into c — which is exactly the bookkeeping all those scope watches were doing. With observables you think differently: a, b and c are streams, and add is an operator. You plumb them together once — pipe a and b into add, pipe its output onto c — and from then on, pushing a new value onto b automatically flows through add and out onto c. No watches. The plumbing is already set up.

That’s how you migrate the form. In Angular a form exposes a valueChanges property, which is an observable that pushes out the JSON of the whole form. You pipe it through operators: filter (only emit when the form is valid), debounceTime (only after, say, 400ms of quiet), distinctUntilChanged (only when something actually changed), maybe a map of your own — then subscribe to do something with the result. So the honest answer to “what do I do with my scope watches?” is that you re-architect and rethink the problem in terms of observables.

Where are all the third-party Angular modules?

This is a question I find genuinely interesting. AngularJS only really took off once there were enough third-party modules to drop in — each one adding a chunk of functionality and speeding up development. People migrating naturally ask: where’s the equivalent for Angular?

The answer is that a huge share of those AngularJS modules — I’ll throw out a number, maybe half — weren’t doing anything special. They were wrappers around vanilla JavaScript libraries. AngularJS only knew about a change if it controlled it: you had to tell it when something happened so it could run the digest cycle and update the screen. If a vanilla library’s callback changed a value AngularJS didn’t know about, nothing happened on screen. So the wrappers existed mainly to call $scope.$apply and kick the digest cycle. That’s the whole job most of them were doing.

Zone.js kills the wrapper

This is where Zone.js comes in — a technology I think is fascinating, and one I’ve submitted plenty of conference talks about that nobody’s accepted yet. (Conference organisers, if you’re reading my abstracts: have another look.) Zone.js is what removes the need to call $scope.$apply at all. Because the new Angular uses Zone.js, when you run a hybrid app your AngularJS half is running inside a zone too — so you never need to call $scope.$apply in hybrid mode.

And because you don’t need it, in Angular you just use the plain vanilla JavaScript library directly — the same one the React and Vue developers are using. That’s the real reason you don’t see a third-party module ecosystem spring up: you don’t need one. A spinner is a good example: instead of a wrapper, you put a template reference variable on the node, grab it with a @ViewChild decorator, and pass that DOM reference straight to the vanilla spinner. No $scope.$apply, no wrapper, nothing.

What replaces $emit and $broadcast?

This one stumped me when it first came up. $emit and $broadcast were AngularJS’s eventing mechanism, and there’s no clean one-to-one replacement in Angular. My honest opinion: heavy use of them is a bit of a code smell — not bad exactly, there are good use cases, but if you’re really struggling to migrate a pile of $emit/$broadcast code, you were probably overusing it. The right Angular solution is to re-architect around inputs and outputs and shared services. There’s no built-in eventing primitive, though it’s trivial to write a service that does the job.

But there is a neat trick for the transition. Earlier I described rewriting something as Angular and downgrading it so AngularJS can use it. You can do the opposite — upgrade an AngularJS service so Angular can use it. So you grab a reference to $rootScope (where all that eventing lived), create an injection token for it, write a factory that pulls $rootScope out of the AngularJS injector, register it as a provider in your NgModule — and now you can inject AngularJS’s $rootScope straight into your Angular code. You’ll have to delete it eventually to finish the migration, but during the process it gives you access to your existing eventing without rewriting everything at once.