Wednesday, May 31, 2017

Angular and the problem with primitive data types in version 2 and above.

What is the problem?

Well, as Angular change-detection is based on detecting actual changes, replacing a value with the same value is not detected. This will make more sense if you read the rest of the article. This is an issue when you use primitives.

But first back to the basics, Primitives

What are primitives? In JavaScript there are 6 primitive data types: string, number, boolean, null, undefined and symbol(es2015) The rest are all some form of Object.
What is the difference? Well, primitives are passed by value. If you assign a new value to a primitive, that is exactly what happens. The memory spot that is used to store the value stays's the same, but its content gets refreshed.
As a sample:
let a = 10; // the memory slot for varable a gets the content 10
let b = a   // the memory slot for variable b gets the content 10
a = 20;     // the memory slot for variable a gets the content 20
console.log(b)  // logs 10;

Composite variables

Ok, what happens to non-primitive variables? Better known as composite variables. Well, those are stored as a reference in memory. A reference is a pointer to the actual contents of the variable. When you assign a new value to the variable itself, it gets a new pointer. When you change any of the content, nothing happens to the reference, it still points at the same spot in memory.

let a = {}; // the memory slot for varable a gets a pointer to the object
let b = a;  // the memory slot for variable b gets the same pointer
a.b = 20;   // the object gets an update, the pointer stays the same
console.log(a === b ) // logs true, as the pointer is the same
console.log(a.b === b.b) //also logs true.
a = { b:20 }  // assing a new pointer to the memory slot of a
console.log(a === b ) // logs false, as the pointer is changed now.

// ok the folling one might be confusing:
console.log(a.b === b.b) // logs true. 
// in the above line I'm comparing primitives that are stored in a composite 

What has this to do with Angular change-detection

Well, back to Angular. Angular is designed for speed. As a result, its change-detection checks for changes. As we have seen above, assigning the same value to a primitive doesn't get detected. Most of the times this is not a problem. However, if you assign a primitive to a property in a template like this:
      <button (click)="moveToTop()">move</button>
      <div class="box" [scrollTop]="scrollDistance">
the change detection will not pick up if you change the value of  `scrollDistance`
if you do this in your controller, nothing will happen because of that:
export class App {
  scrollDistance = 1;
  name:string;
  constructor() {
    this.name = `Angular! v${VERSION.full}`
  }
  moveToTop(){
    this.scrollDistance = 0;
  }  
}
This doesn't work as expected, updating the value of scrollDistance does update the value. So, only when the value changes, the view will receive the update. The second time we push the button, the value 0 is already there, as 0 === 0 so there is no change. Hence nothing happens. Turns out, there is no way you can detect if a primitive is overwritten with the same content. As this is a perfectly valid use-case, of course, Angular provides us with a solution for this.

Angular to the rescue! Introducing the WrappedValue helper

WrappedValue (link to docs) is the rescue out of this. This class is provided by Angular itself and used internally. It is exposed by the core module exactly for cases like this.  It wraps the primitive into a class and makes sure its picked up by the change-detection, by providing a new reference every time you use it.
it works like this:
   this.scrollDistance = WrappedValue.wrap(0)
Now the change-detection will pick up the new reference from the wrapper, and Angular knows what to do with that. Problem solved.

click here to see this demonstrated in a plnkr, originated by Rob Wormald.