The Angry Angular AsyncPipe & The Evil Elvis Operator

Written by visikov | Published 2017/04/06
Tech Story Tags: angular2 | javascript | prototyping | async | compilers

TLDRvia the TL;DR App

The async pipe and elvis (?) operator are very useful…

But only when you’re prototyping||developing.

T_hese seemingly benign and super convenient features are actually a cause for concern. While they’re minor changes to the template there’s a lot more going on under the hood than you might think. Plus, they’re bad practices that hide the underlying issue, poorly written code… but we’ll get to that later._

First, a little introduction to the way that angular actually uses that html template that you wrote. Angular has to compile that html and turn it into javascript, beautiful, beautiful javascript. At the least the compiler does the best that it can, you know… with all those Elvis operators and AsyncPipes you’ve got going.

The compiler turns this template into an ngFactory. If you’re using AoT (Ahead-of-Time) compilation then this an actual file that gets generated on your file system and you can actually open it up to read it, inspect it and edit it. It’s an awesome learning experience.

Great fun, many headaches, lots of grey hairs.

Otherwise, the compiler creates the ngFactory at runtime, in memory, in the browser… for every client, but that’s a topic for another post. All you need to know for now is that the ngFactory is what renders your view. For simplicity, think of it as a bunch of document.createElement calls with some event listeners for change detection and other magic.

Breaking Down the Factory

The code for the ngFactory changes pretty dramatically when you add either of these features, specifically the arguments that are passed into the interpolator in the change detection method, detectChangesInternal.

A simple string interpolation looks like this, so we have a basis of comparison

{{ response.email }}

This is pretty straightforward. If there’s a change, detectChangesInternal fires off and interpolates the string, this.context.response.email, and sets that as the value of currVal_0. Then it sets the text value of this._text_1, which is an element created with renderer.createElement(), to our interpolated string.

Elvis’s, Elivs’, Elvi or Elves?

This operator prevents a lot of runtime errors. If you attempt to access a deeply nested property that doesn’t exist, you’re gonna have a bad time. That’s where this operator comes in. But before you start writing something like this:

{{ user?.account?.service?.joined?.dateFormatted }}

Here’s what we get, I’ve formatted it a few different ways

That’s just to render 1 string on 1 page. Four nested ternary operators. Now image what this would look like if your template had something like this in it:

Service: {{ user?.account?.service?.name }}Formatted: {{ user?.account?.service?.joined?.dateFormatted }}Raw: {{ user?.account?.service?.joined?.dateRaw }}

Throw some functions in there maybe an observable or two…

*ngFor="let user of users"

Then multiply that by n number of components… this becomes a nightmare. Real quick.

And people are wondering why their ng2 app is running slow and choppy.

Thankfully, there’s ways to avoid this. Details below.

async - Only 5 Characters, right?

The async pipe adds the reset and unwrap methods from ValueUnwrapper into the ngFactory, these are just some helper functions that we don’t really need to get into. The meat is in the transform method from AsyncPipe, which basically wraps the value in an observable (or sometimes a promise).

Let’s take a look at what the following compiles into

{{ (response | async) .email }}

The compiler also added another check in the conditional, which makes sure that the value coming back from AsyncPipe.transform is a wrapped value (value.wrapped instead of just value). Not a very expensive operation, but still adds some complexity.

The part I want to focus on is the AsyncPipe.transform. As mentioned above, this method creates an observable and subscribes to it (magic). When the observable emits a value, the AsyncPipe marks the ViewRef for checking, think AngularJS dirty checking. But it’s different in ng2.

The change detection bubbles up to the root element. That means every component between the one with the AsyncPipe and root have their change detection methods triggered (marked as dirty/forCheck).

There’s a few more things going on, but you get the point. That’s a lot of code execution and added complexity.

If this pipe was added to a component that was 5 levels deep, it can take a huge toll on your performance. Albeit, the angular team did some things right with their implementation of change detection. If n amount of async pipes emit a new value before a change detection is actually ran, then each of the marked components are checked only once.

However, the exact opposite can happen where each of those subscriptions emitting a value in series and the involved components get checked multiple times, which can be a very expensive operation.

Conclusion (how fix)

Both the elvis operator and the async pipe can be dealt with in one swift blow.

State Management - 1 source of truth

Your state management solution is:

  • The ultimate source of truth - AppStore
  • Immutable
  • Describe the shape of your data - provide default values

That last one is the important one. If your component is subscribed to your state, or a property of it, instead of an http request then the AppStore can immediately emit default values to the component in the exact shape that the component needs it in. The component can then fire off a method on a service, the service sends off an API call, when it’s done it will update the state and finally, triggered by a change in state, the AppStore emits new data to the component.

Wow. Such Simple. Many Great.

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMIfamily. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

To learn more, read our about page, like/message us on Facebook, or simply, tweet/DM @HackerNoon.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!


Published by HackerNoon on 2017/04/06