Skip to main content
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: John Whish and Kev McCabe
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: John Whish ( @aliaspooryorik ) Kev McCabe ( @bigmadkev )

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

By 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.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel