AngularJS migration — use redux to nuke $scope soup

Refactoring $scope soup with reactive(redux or rx) event stream components

Pascal Maniraho
Simple
8 min readNov 22, 2017

--

Photo credit Artem Sapegin from Unsplash

Intro

This article helps you pave a way toward a successful code migration from your large scale Angular 1.x to Angular+. Same concepts can also be applied when migrating to react instead.

We’ve got you covered If you want to Transition to React instead.

Synopsis

The new Angular framework favors reactive based one-way data binding over dirty-checking-based bi-directional data binding.

To break away from $scope, while keeping third party library integration, more efficient Zones were introduced. “A Zone is an execution context that persists across async tasks.” ~ angular/zones. This blog highlights techniques to remove dependency of $scope while in Angular 1.x, to produce faster Angular ready code, without breaking things.

Nota: This is the 5th in a series of blog posts about soft code migration, as I move Hoo.gya platform that helps you to rent various stuff to/from your friends, neighbours and coworkersfrom Angular 1.x to Angular 2/4/5/X. Your recommendation (👏 👏 ) motivate to followup with a new article. Leave questions in comment section below, I will help however I can. Other articles are: First article, Second article, Third article, Fourth article, You are currently reading the 5th article. I hope this article helps you.

Observables

With Angular (2 and 4.x.x), the scope was removed completely. It is dead. Angular relies on context aware, asynchronous event based, change detector Zones as a replacement of digest cycle(synchronous) dirty checker.

$scope

Using scope’s dirty checking for bi-directional binding was the selling point for the then new framework. As you are aware of, this technique degrades performance as the codebase grows(or over 2k bindings). The lack of the model in MVC contributed state management nightmare, and hack-ish over-use of $scope.$watch and $scope.$apply. There is a better way: (Shared) States.

Shared State

One data store able to communicates change to subscribed agents, either via a pubsub or observer pattern(the cheaper the better) sums all about achieving single source of truth. Redux, is a light-weight throw-in library that just does that. Zones will handle events(later with Angular+), and Redux/Rx will manage application states(model) and synchronization.

Redux

Consistency and predictability of your code structure, will add long term value, for future code migrations or day-to-day give code refactoring. Redux is built on 3 main components: store as state keeper, reducers as state mutators and actions as message buses.

Store

The store serves single source of truth. It features subscribe() and dispatch() for Observer pattern implementation, and a getState() to read the state. Since this concept is not native to Angular 1.x, there is a clear need to make it work with Angular: via a service.

Reducers

Redux is designed with one-way data flow at its core. Reducers are the only utilities allowed to mutate Store’s state. Quick reminder is mapReduce computing concept, were the reduce operation(or combiner) derives a new states from initial mapped data sets.

Actions

Actions moves message payloads around. Think of them as message buses in redux sub-system. Every payload has a type and associated value. The type tells reducers what to, and the value is the actual data. Action Creators are factories of these payloads. Action creators on another hand, are utilities functions that helps to create actions.

Patterns

Angular Component implementation gave birth to lifecycle hooks such as $onInit, $onChange or $onDestroy to name a few. Some events that lead to state mutation range from HTTP Response (resolved promise), WebSocket message reception(Push Message), Web Worker or Service Worker Events, iFrame’s postMessage Event to name a few. These are common places to look for ways to restructure our code. When new data reaches our those parts, the reducers needs to mutate the state, and Store notifies subscribers about the change via dispatch().

Dispatch

Events

Angular 1.x used $scope.$emit and $scope.$broadcast to dispatch custom events. Event handlers were handled by $scope.$on callbacks. Most of these event emitters served to pass messages to components deep buried in Angular apps, that would otherwise work with $scope.$watch construct. Quick example being a new chat message hitting WebSocket endpoint from server, a $rootScope.$broadcast(‘new:message’) may have been used to notify other sub-systems concerned with the new incoming message.

$scope.$on

Components concerned with an event implemented handler constructs to be used as “.$on” callbacks. Outside current context about state mutation. That makes them good candidate to be replaced with pub/sub(redux/ngrx). Custom events are good candidate to replace with subscribe. If event notifies about state mutation, then subscribe callback can easily update current state.

Takeaway is: Compared to $emit-ting or $broadcast-ing notifications about state mutations, It is more elegant to stream(dispatch) next state change events to shared state, and delegate the change to be committed by reducers.

$scope.$on(‘$destroy’)

Component implementation has lifecycle hook $onDestroy(or ngOnDestroy for Angular+), that can be be used for this special case.

$scope.$watch

Most, if not all, “$watchconstructs were designed to execute in case of state mutation. There is “ng-change that can help to prepare migration. The new Component has “onChange hook, that can be used for parent-child uni-directional binding. As a quick example, reacting to search text field change.

$scope.$apply

Not really required, but callbacks that have been used inside $scope.$apply can be decoupled for future use. The refactoring of Directive with Linking functions(jQuery plugins, etc) will be subject of the last article.

WebSocket

Streaming WebSocket messages to redux managed store.

Services

How services can be improved or refactored? That is subject to next article in making. Meanwhile, It worth to mention that this pattern will occur more than once.

Component

The last pattern worth to mention is the redux code in Components. The rule of thumb is to have a model, that can be updated every time there is state mutation. The destructor will de-register from such updates when the component dies.

Conclusion

Angular 1.x impact performance of large scale apps. ControllerAs notation introduced in Angular 1.3 was in part to tackle performance issue, by reducing the number of functions directly attached to the $scope. Likewise, to offload state management off $scope, redux improve overall performance while increasing readiness to embrace a new framework. Since you are planning migration to Angular (2/4/5 any x really), less logic bound to scope translates into saving time and money.

Additional reading material.

Closing note

Gosh, You rock If you read all above goodness! Thank You! Like last time, YOUR recommendation(👏 👏 👏) motivates me to follow up this post with techniques I use while upgrading Hoo.gy — a platform that makes it possible for you to rent stuff from your friends and neighbors. It is green sharing economy, to curb consumerism and save you thousands in credit card debts, and our planet ;-) … Also join the waiting list, I never send emails unless there is really a very big announcement!

--

--

Pascal Maniraho
Simple

Web lover, code crafter, beer drinker, created http://hoo.gy, Montrealer, and training to run a half-marathon :-)