Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at BFusion / BFLEX 2009 (Bloomington, Indiana) with: Simon Free and Dee Sadler
Ben Nadel at BFusion / BFLEX 2009 (Bloomington, Indiana) with: Simon Free@simonfree ) and Dee Sadler@DeeSadler )

Inline Object Literals Don't Trigger Unnecessary Input Changes In Angular 2 RC 2

By Ben Nadel on

A few weeks ago, I experimented with dynamic template rendering in Angular 2 RC 1. As of Angular 2 RC 2, dynamic template rendering is now part of the core platform functionality. But, the context input for the given template is provided as an object, not as a set of individual inputs (as I had in my experiment). This got me thinking about object literals and how they would affect input change detection. At first glance, it seems that Angular 2 is handling object literal inputs with a smarter approach than it did in Angular 1.x.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

If you look at the code for the NgTemplateOutlet core directive, you will see that it destroys and re-renders the template if either the TemplateRef or the Context inputs change. This is really what got me thinking about literals. Specifically, I wanted to see if the given TemplateRef would be destroyed and recreated on every digest simply because I supplied the context as an inline object literal.

To test this, I created a small demo in which I am creating and consuming a TemplateRef in my root component. When consuming the TemplateRef, I'm passing-in the [ngOutletContext] property as an object literal. Then, I log the lifecycle of the template using an embedded directive, LogLifecycleDirective.

  • // Import the core angular services.
  • import { Component } from "@angular/core";
  •  
  • // Import the application components and services.
  • import { LogLifecycleDirective } from "./log-lifecycle.directive";
  •  
  • @Component({
  • selector: "my-app",
  • directives: [ LogLifecycleDirective ],
  •  
  • // When we define the <template> OutletContext, notice that we are using an inline
  • // object literal as a means to provide the TemplateRef context. In Angular 1.x,
  • // this object literal expression would have ended up creating a new object reference
  • // in every digest. In Angular 2, however, the compiled proxy functions compare the
  • // object keys before returning the necessary object reference. As such, Angular 2
  • // doesn't end up creating a new object on every digest which, in turn, means that it
  • // doesn't trigger unintended Input changes on the target directives.
  • template:
  • `
  • <p>
  • <a (click)="setThings( 'apples' )">Set Apples</a> &mdash;
  • <a (click)="setThings( 'bananas' )">Set Bananas</a> &mdash;
  • <a (click)="incrementCounter()">Increment counter</a>
  • </p>
  •  
  • <template
  • [ngTemplateOutlet]="myTemplate"
  • [ngOutletContext]="{ items: things }">
  • </template>
  •  
  • <template #myTemplate let-items="items">
  •  
  • <p logLifecycle>
  • How do you like them {{ items }}?!
  • </p>
  •  
  • <p>
  • Counter: {{ counter }} <em>(from lexical context)</em>.
  • </p>
  •  
  • </template>
  • `
  • })
  • export class AppComponent {
  •  
  • // I hold the counter (which is being rendered in the template via a lexical binding).
  • public counter: number;
  •  
  • // I hold the type of things (which is being rendered in the template via the
  • // ngOutletContext and the template-local bindings).
  • public things: string;
  •  
  •  
  • // I initialize the component.
  • constructor() {
  •  
  • this.counter = 0;
  • this.things = "apples";
  •  
  • }
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I increment the counter by one.
  • public incrementCounter() : void {
  •  
  • this.counter++;
  •  
  • }
  •  
  •  
  • // I set the things.
  • public setThings( newThings: string ) : void {
  •  
  • this.things = newThings;
  •  
  • }
  •  
  • }

As you can see, I'm supplying the template context as the inline object literal:

{ items: things }

The root component exposes three links - two of which change the inline object's value and one of which is unrelated. The unrelated link will still trigger change-detection in the root component which will help us see how the object literal dovetails with the change detection strategy and the digest lifecycle.

Embedded within the template, I'm using a simple directive that logs its own lifecycle so we can see when the TemplateRef is created and destroyed:

  • // Import the core angular services.
  • import { Directive } from "@angular/core";
  • import { OnDestroy } from "@angular/core";
  • import { OnInit } from "@angular/core";
  •  
  • @Directive({
  • selector: "[logLifecycle]"
  • })
  • export class LogLifecycleDirective implements OnDestroy, OnInit {
  •  
  • // I get called once when the directive is being destroyed.
  • public ngOnDestroy() {
  •  
  • console.log( "Directive destroyed." );
  •  
  • }
  •  
  •  
  • // I get called once when the directive has been initialized and the inputs have
  • // been bound for the first time.
  • public ngOnInit() {
  •  
  • console.log( "Directive initialized." );
  •  
  • }
  •  
  • }

Bringing this all together, when we run the above code and click the three links in series - setting apples, setting bananas, and incrementing the counter - we get the following output:


 
 
 

 
 Inline object literals and input change detection in Angular 2 RC 2. 
 
 
 

As you can see, we only log one re-rendering of the TemplateRef. The first link - setting apples - didn't do anything because the TemplateRef was already being rendered with that value. The second link - setting bananas - triggered a change in the template inputs, which destroyed and re-rendered the TemplateReft. The last link - incrementing the counter - didn't affect the TemplateRef rendering (other than updating the internals of the view itself).

What we can conclude from this is that the use of an inline object literal doesn't create a new object reference in every digest cycle. And, in fact, if we add some breakpoints (which I do in the video), we can see that Angular is, indeed, looking at the dynamic portions of the object literal before it determines which object reference to use as the input. This is very good to know and makes the NgTemplateOutlet directive much more useful than I had at first believed it to be.




Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.