The Road to Angular 2.0 part 5: Bindings

Intro


I gave a presentation at the GOTO conference in Amsterdam titled: The Road to Angular 2.0. In this presentation, I walked the Road to Angular 2.0 to figure out why Angular 2.0 was so different from 1.x.

This series of blogposts is a follow up to that presentation.

Bindings


Last week we discussed Components, which showed us a fundamental new way to think about our Angular applications.

This week we are going to look at bindings, aka the way Angular automagically updates values in our views. The Angular 2.0 team put a lot of effort in making this system faster. The team reports a speed increase that is 3x to 10x times faster than Angular 1.x.

But something had to change fundamentally in order for this speed increase to be possible.



Bindings in Angular 1.x


Before we can understand why Angular 2.0 is faster than 1.x, and how the Angular 2.0 team did it, we must first look at how Angular 1.x handles bindings.

An Angular 1.x App


Let's begin with a fictive Angular 1.x app which has four bindings: A, B, C and D, these bindings have relationships (or dependencies) with each other as depicted in the the image below:

Angular1x-app

This image above shows that binding A has a relationship to binding B and vice versa. This means that whenever A updates something might 'change' for component B, but it doesn't necessarily have to. The same is also true in reverse: when B changes A might also need an update.

There can also be relationships that are 'one' sided, for example A only influences D but not the other way around

Bindings can also have subtle relationships to other bindings indirectly: B has a relationship to binding D through binding C.

The point is that relationships between bindings in Angular 1.x can become pretty complex. Even when you have only four bindings. So how does Angular 1.x know when to update a 'binding' and
show a different value inside the UI? The answer is dirty checking.

Dirty Checking


Dirty checking can be explained as follows: every time there might have been a change to the view, so whenever the user clicks on something, an $http request finishes, Angular will check the value of each binding and compare it to the old value. When the value is different between the two versions Angular will update the UI. When a value differs between it is considered 'dirty', hence the term 'dirty checking'. The phase in which Angular checks for changes is called the "digest phase".

To make this example more concrete: let's say we have a variable called "age" and the value is currently 16. Then some event triggers the digest phase, now the new value of "age" is 17. Angular will compare 16 and 17 and it will say that a change has occurred and update the UI.

But what if you have a binding that depends on another binding, how does that get updated? We know, as humans, what the 'relationship' between two bindings is, and which one depends on the other, so we know intuitively in which order to evaluate the bindings. But how does Angular know which binding to evaluate first?

The answer is that Angular 1.x doesn't know anything about the relationships between 'bindings'. So it cannot know in which order to perform the 'dirty' checks. What Angular does instead is evaluate each binding until all bindings have stabilized, by stabilized I mean that they have stopped changing. Angular does this by running a "loop" that will evaluate each binding until the bindings “report” that they are stabilized.

This "loop" is called the digest cycle, which is a part of the digest phase. The digest cycle will resolve all bindings until none of the bindings have reported a change between runs of the cycle. The digest cycle is a subpart of the digest phase:
Digest-Phase

If you have two bindings with a relationship, it might occur that the digest cycle needs to run multiple times before both bindings no longer change. If the bindings does not stabilize after 10 cycles Angular gives up and you get an error. This magical limit of 10 is called the Time To Live, and you can even increase or decrease it if you want to.

When the digest cycle reports to the digest phase that it is complete and that the system is stable, only then will Angular re-render the views.

So to conclude: the digest cycle is a clever way for Angular 1.x to not know what your 'relationships' between bindings actually mean semantically, but still update those relationships in your UI correctly.

What’s wrong with 1.x bindings?


The Angular 1.x way of resolving bindings, and their complex relationships, is a really great way to solve a very complex problem. However there are three downsides to the 1.x approach:

Expensive


Resolving bindings with complex relationships by checking them in a digest cycle can lead to suboptimal performance. If you have a very complex application with multiple complex relationships between bindings, it may take Angular 1.x’s digest cycle multiple loops before it can report, to the digest phase, that the system has stabilized.

In this sense resolving bindings can potentially become very expensive.

Unpredictable


The system is also very unpredictable, if you gave me the following template:
<div ng-controller="PersonController as personController">
  <h1>Hi {{ personController.person.name }}</h1>
  <person-view person="personController.person"></person-view>
</div>

And you would ask me: “How do the bindings get resolved, in this template?” I would not be able to give you an answer straight away. I would have to dive deep into the “PersonController” and into the “personView” directive before I can provide an answer, for instance what type of binging is “person” on personView? My answer would depending on what type of binding it is, but even I could not tell you how Angular would actually resolve the binding in the digest phase.

Basically the Angular 1.x bindings system is not deterministic, if you gave it the same inputs it might take a different route each time to get to the outcome. This property of the system makes it difficult to reason about an Angular 1.x application.

Unnecessary


Immutable


Bindings can sometimes even be unnecessary, consider the following: What if you know that a data structure never changes. For example: what if you rendered a menu based on an array of strings, and you knew that there was no possible way that array would ever change. In other words you know that you will never change the menu during the run of the application.

Doing dirty checking on such an immutable (never changing) data structure is a pure waste of time. There is no way to tell Angular 1.x that this structure needs to be absolved from the digest phase, so it gets evaluated each and every time.

Observable


Another situation can be that you have a component, which only changes when a specific event occurs. In other words the object will never change unless that particular event is fired. Again in Angular 1.x there is no way to tell the system that such a component exists. The system will dirty check that component even though we humans know it is futile.

Bindings in Angular 2.0


Now that we know how Angular 1.x handles bindings, and we know some of its flaws, we can look at how Angular 2.0 mitigates these flaws and improves on the system. Before we can do that we must first look at the anatomy of an Angular 2.0 application, because last week we learned that Angular 2.0 will be a component based framework, how does being component based affect the binding system?

Anatomy of an Angular 2.0 application


Lets say we have an application that provides us with weather information of various cities in the world. The application looks something like this:

weather-app

The application consists of a Grid of WeatherStation’s each station consists of a name, temperature, humidity and an icon telling the current state of the weather. You can favorite a weather station by clicking on the “star” icon. Above the Grid are two bars, a SearchBar in which the user can filter the stations based on the name, and a SegmentedButton in which the user can toggle between All stations and the user’s favorite stations.

This application is written in Angular 2.0 so therefore it is component based. Components are composable, which means components can be nested inside of each other. The weather app’s structure looks something like this:

tree-components

Each component is a direct or indirect descendant of one “root” component, in this case the WeatherApp component. This leads to an important realization: Angular 2.0 applications are trees!

What is so great about trees?


Trees are very easy to understand, because the relationships between components are instantly obvious. Compare the image of the relationships between bindings in Angular 1.x with the image of the weather apps tree, the relationships in Angular 1.x could quickly run out of control.

In Angular 2.0 there only exists one type of relationship between two components: A component can either be the parent of the other component, or a child of that component. In that relationship the parent component can send information down to the child component, and the child component can send events back to its parent.

The relationships between components in Angular 2.0 can be codified as follows:

bindings-codified

Having this property makes it very easy to reason about components when you encounter them in a template:

<grid>
  <div *for="#station of stations">
    <station [station]="station"
     (station-changed)="stationsDidChange()">
    </station>
  </div>
</grid>


In the template is is immediately clear that the [station] binding comes from the parent, in this case from the Grid’s “stations” property. It is also simple to deduce that the “(station-changed)” event calls “stationDidChange()” on the Grid component, because Grid is the parent of the Station component. In Angular 2.0 you can read a template and instantly understand the relationships between components.

The fact that Angular 2.0 applications are trees also influence the way the digest phase works. In 2.0 there is no more need for a digest cycle, because in order for Angular to resolve all the bindings it only needs to go from the top of the tree to the bottom of the tree once.

The reason for this is simple: a component can only receive data ([] bindings) from its parent component, ergo a child component can only evaluate its bindings when his parent bindings are resolved. So a child must wait for its parent. A parent cannot receive data from his children, because bindings only go down, this means that child components bindings cannot influence the parent. In other words Angular only needs to reach the “bottom” of the tree and it is done.

Here you can see the contrast between Angular 1.x and Angular 2.0’s change detection visually:

change-detection-difference

I listed Angular 1.x’s bindings system downsides it as being expensive, unpredictable and unnecessary. Given what we now know about Angular 2.0 you can say its system is not expensive, because there is no digest cycle anymore, which can behave sub optimally. The system is now predictable because the flow of data and events is clearly defined, so we humans can reason with it better. But what about “unnecessary” can we stop Angular 2.0 from doing things we know are not needed? Yes we can!

Change Detection


In Angular 2.0 we can take over the way Angular does change detection on a per component basis. This enables us to squeeze even more performance out of Angular 2.0 when it is absolutely needed.

By default Angular 2.0 will generate a change detector “class” for each component at runtime. So if you have a component called WeatherStation Angular 2.0 will generate a WeatherStation_ChangeDetector class. This class will read the “meta” data about your component, so all inputs and outputs, and generates a class that will do the dirty checking. This is why you have to state all the input and output for your components.

For example if the WeatherStation only has a “temperature” property this class might look something like this:
var temperature = obj.temperature;
if (temperature !== this.previousTemperature) {
  this.previousTemperature = temperature;
  this.weatherStation.temperature = temperature;
}

The reason Angular 2.0 generates such a specific “class” for every component is because JavaScript virtual machines (VM) can optimize the hell out of specific “code” way better than they can optimize “generic” code. In very technical terms VMs can optimize monomorphic code better than polymorphic code. Here is a great blogpost by Vyacheslav Egorov explaining why this is true. Angular 1.x could not be optimized well by VMs because it used a polymorphic checking algorithm.

Here's the kicker: you can tell Angular that you want to implement the _ChangeDetector class yourself. This enables us to write immutable components and observable components. In fact the “immutable” behavior comes built in with Angular 2.0.

To make an component immutable you can do this:
@Component({
  changeDetector: ON_PUSH
})

This means that the @Component will only run the change detection when new bindings are pushed into the component. So when the component never receives new bindings it is never part of the digest phase. Which means that Angular 2.0 will not waste time checking something that we know will not change.

The cool thing about being able to set the way change detection works per component is that you can mix and match various strategies. Parts of your application can be immutable, parts can be “default” and some parts some exotic strategy you can come up with.

This gives us some powerful tools to prevent Angular from doing “unnecessary” things, which slow our apps down.

If you come out of this thinking you must declare your own “change detection” algorithm to get the performance boost Angular 2.0 promises you are wrong. Just the fact that Angular 2.0 applications are trees gives it a boost in performance alone. You can steer clear of defining you own strategies and it will still be fast, it is just there when you need it.

Graph Time


Speed


By now you want to see a graph showing that Angular 2.0 is faster, so here you go:

Speed

The graphs shows the performance of the same application written in various ways. On the left you see a red bar, which is a baseline application written in vanilla JavaScript. This baseline application is written in the most optimized (but ugly) JavaScript imaginable. It has zero levels of abstraction, every level of abstraction comes with a price in the form of less speed, so it is the fastest way to write a HTML application. The Angular 2.0 team uses this baseline application to see how fast they can get.

The blue bar on the right represents Angular 1.3: it's 8.58 times slower than the baseline application. Next to that is Angular 2.0 in orange, this represents a “fresh” Angular 2.0 application. Next to that is a “green” bar which represents Angular 2.0 in a “hot” state, which means that it has cached some views.

As you can see a “fresh” Angular 2.0 is 3 times faster than 1.x. What is even nicer is that the more you click through an Angular 2.0 application the faster it becomes, at least two times faster. Angular 2.0 will provide view caching for you automatically.

Memory Pressure


The memory Angular 2.0 uses is also down dramatically as seen in this graph:

Memory

Memory efficiency is increasingly important in this mobile world that we live in, mobile devices do not have as much memory as their desktop cousins. Angular 2.0 prides itself on being a mobile first framework, so they have to take their memory consumption seriously.

The Angular 2.0 team announced at the Angular U conference's keynote that they are not done optimizing yet! So expect even more speed when 2.0 is finally released.

Want to know more?


Victor Savkin a core contributor to the Angular project has some great blogposts about bindings:
• http://victorsavkin.com/post/114168430846/two-phases-of-angular-2-applications
• http://victorsavkin.com/post/110170125256/change-detection-in-angular-2
• Or you can watch Victor explain it in a twenty minute video.

Conclusion


To make Angular 2.0 faster than ever, the nature of an Angular application had to change from a cyclic graph, to a tree. A cyclic graph is by nature very complex, it can point to everything from anything, a tree is nice and simple and only points down.  Having a tree makes Angular 2.0 applications easer to reason about. The speed and memory pressure graphs speak for themselves, the Angular team have outdone themselves, and they are not finished yet.

We have walked to Road to Angular 2.0 now and we have seen most area's in which Angular 2.0 is different from Angular 1.x. What we haven't talked about is how to cross the Rubicon ourselves: how de we migrate our own Angular 1.x applications to Angular 2.0. That is the topic of the final installment of this series.

No comments:

Post a Comment