14
votes

So I read this article about Angular 2 change detection, but after reading it I have got more confused, so I started reading some of the comments which led to more confusion.

Immutable Objects

If a component depends only on its input properties, and they are immutable, then this component can change if and only if one of its input properties changes. Therefore, we can skip the component’s subtree in the change detection tree until such an event occurs. When it happens, we can check the subtree once, and then disable it until the next change (gray boxes indicate disabled change detectors).

So if {{todo.text}} or todo.checked change we mark our todo:Todo that a changed has occurred Immutable objects

But then from my understanding, we can create a cascade of immutable objects.

If we are aggressive about using immutable objects, a big chunk of the change detection tree will be disabled most of the time.

cascade immutable obejcts

@Component({changeDetection:ChangeDetectionStrategy.OnPush})
class ImmutableTodoCmp {
  todo:Todo;
}

So in this case, any changes on {{todo.text}} or todo.checked won't be noticed right? only when a Todo is pushed we will see a change?

Observable Objects

If a component depends only on its input properties, and they are observable, then this component can change if and only if one of its input properties emits an event. Therefore, we can skip the component’s subtree in the change detection tree until such an event occurs. When it happens, we can check the subtree once, and then disable it until the next change.

Although it may sound similar to the Immutable Objects case, it is quite different. If you have a tree of components with immutable bindings, a change has to go through all the components starting from the root. This is not the case when dealing with observables.

I don't get it how Observables are quite different from Immutables and in this specific case of the Todo app, which approach is better?

2

2 Answers

22
votes

So, like we are 5 year olds from now on.

This is JohnCmp. John is a component that accepts two inputs, a name: {first: 'John', last: 'Smith'} and an age: 20.

What happens if name is mutable? Well, someone could pass it to John and hold a reference to it. So that John, and ANY number of other objects or services can hold a reference to that name object. This means they can change it, say doing name.last = 'foo'. And John's name changes now, but he didn't receive a new name. He still has a reference to that name object, but it mutated.

If we want to detect this, we have to aggressively check name, name.first, name.last, and so on with every property of every object we pass around. How much easier would it be to do name === newName and just compare references, hey? If name is immutable, we don't need to go crazy and check every property, we can check references and know if an object is changed quickly.

Ok, now, imagine nobody holds a reference to John's name object. So if they want to give him a new name, they must pass in a NEW name object. Then John changes only when his input changes. This is what is meant here:

If a component depends only on its input properties, and they are immutable, then this component can change if and only if one of its input properties changes. Therefore, we can skip the component’s subtree in the change detection tree until such an event occurs.

Alright, so that's the basic case right? Now we don't have to worry about checking every property, we just check that references haven't changed. Much improved! BUT objects can be large. So a people array is an immutable Array of Johns, which are immutable, of names that are also immutable. So in order to change John's name. you need to produce a new name, and a new John, and a new people array.

So they all change, the whole tree needs to be traversed. So that's O(n). Hence this little comment:

If you have a tree of components with immutable bindings, a change has to go through all the components starting from the root.

BUT

This is not the case when dealing with observables.

Why tho?

Observables emit events. They don't need to change everything like immutables, they just need to emit a change event. So in his example, you don't have an immutable "array" that you need to recreate and therefore re-evaluate all the changes up the tree. Now you have a people observable, that triggers events when it changes. And John receives an observable as well.

So you can just trigger an event in John telling him that his name changed, WITHOUT triggering an event in people or things of the sort. Avoiding you to re scan everything for changes, thus reducing complexity to O(log n), as per this quote:

As you can see, here the Todos component has only a reference to an observable of an array of todos. So it cannot see the changes in individual todos.

(emphasis mine).

Hope this helps.

9
votes

I don't get it how Observables are quite different from Immutables and in this specific case of the Todo app, which approach is better?

Imagine that you have a tree of nodes, and each node has a bit that says "I may have changed". With Observable, we are talking about events getting emitted; the event is dispatched up the tree. In other words:

  • Todo_ChangeDetector would be flagged,
  • then walking up the tree, Todos_ChangeDetector would be flagged,
  • then walking up the tree, App_ChangeDetector would be flagged,
  • then the normal change detection kicks in.

Imagine you have 2 todo lists:

  1. a grocery list.
  2. a list of meetings (work?) for the day.

Which grocery list is active you are currently showing would be the Observable; e.g. a dropdown, nav pills, etc. All the todo items under those 2 lists could be Immutable; e.g. you are not the meeting organizer and cannot change the meetings.