Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Juan Agustín Moyano
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Juan Agustín Moyano@jonwincus )

Rendering A List Of Mixed Types Using NgFor And NgTemplateOutlet In Angular 9.0.0-rc.5

By Ben Nadel on

Earlier this year, I looked at rendering a list of mixed-types using ngFor and ngSwitch in Angular 7. The technique outlined in that post is the technique that I generally use. However, as of late, I've been doing a lot of experimentation with various use-cases for ng-template; and, it recently occurred to me that I could also render a list of mixed data-types usingng-template and ngTemplateOutlet. But, I wasn't sure how this would "feel"; so, I wanted to put together a quick demo in Angular 9.0.0-rc.5.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

In Angular, we can use ng-template to either define a TemplateRef instance; or to render a TemplateRef via the ngTemplateOutlet directive. In fact, we can use the two use-cases together to create some pretty powerful functionality, such as the recursive rendering of ng-template fragments.

For this blog post, I'm going to combine the two use-cases to render a ngFor list of mixed data-types. But, before we look at the code, let's take a minute to understand how Boolean operators work in JavaScript, as that is the key-driver of this technique.

The And operator - && - will evaluate a list of expressions and short-circuit on the first Falsy value. The result of the overall expression is then the last evaluated sub-expression. Therefore, the following expression:

true && 1 && 0 && true

... results in 0 as this is the last-evaluated sub-expression that short-circuits the overall expression.

Similarly, the Or operator - || - will evaluate a list of expressions and short-circuit on the first Truthy value. And, just as with the And operator, the result of the overall expression is then the last evaluated sub-expression. Therefore, the following expression:

false || 0 || 1 || true

... results in 1 as this is the last-evaluated sub-expression that short-circuits the overall expression.

ASIDE: The evaluation of Boolean statements is an absolutely fundamental part of JavaScript. If the above examples don't feel natural for you, I recommend that check out the Mozilla Developer Network doc on Logical operators. Honestly, that page is far more interesting (and more important) than my blog post :P

In my demo, I'm using this type of Boolean evaluation to determine which TemplateRef to render in a given ngTemplateOutlet. To set this up, let's quickly look at how our data is being defined. Here, in my App component, I have a mixture of InVision projects that contain both Prototypes and Boards:

// Import the core angular services.
import { Component } from "@angular/core";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

interface Prototype {
	type: "prototype";
	id: string;
	name: string; // This property is unique to this type.
}

interface Board {
	type: "board";
	id: string;
	title: string; // This property is unique to this type.
}

@Component({
	selector: "app-root",
	styleUrls: [ "./app.component.less" ],
	templateUrl: "./app.component.html"
})
export class AppComponent {

	public projects: ( Prototype | Board )[];

	// I initialize the app component.
	constructor() {

		this.projects = [
			{ type: "prototype", id: "p1", name: "P1" },
			{ type: "prototype", id: "p2", name: "P2" },
			{ type: "board", id: "b1", title: "B1" },
			{ type: "prototype", id: "p3", name: "P3" },
			{ type: "board", id: "b2", title: "B2" }
		];

	}

}

As you can see, the Prototype and Board data-types each contain a different set of properties. However, we are going to render them both in a single list. As such, we will have to differentiate which project-type is being rendered in each list item iteration.

In the App component's template, I am rendering the list of projects twice: first using my "traditional" approach of ngSwitch; and the second using ngTemplateOutlet:

<h2>
	Projects (Using NgSwitch)
</h2>

<ul>
	<li
		*ngFor="let project of projects"
		[ngSwitch]="project.type">

		<ng-template [ngSwitchCase]="( 'prototype' )">
			{{ project.name }}
		</ng-template>

		<ng-template [ngSwitchCase]="( 'board' )">
			{{ project.title }}
		</ng-template>

	</li>
</ul>

<h2>
	Projects (Using NgTemplateOutlet)
</h2>

<ul>
	<ng-template #prototypeRef let-prototype>
		{{ prototype.name }}
	</ng-template>

	<ng-template #boardRef let-board>
		{{ board.title }}
	</ng-template>

	<li *ngFor="let project of projects">

		<ng-template
			[ngTemplateOutlet]="(
				( ( project.type === 'prototype' ) && prototypeRef ) ||
				( ( project.type === 'board' ) && boardRef )
			)"
			[ngTemplateOutletContext]="{ $implicit: project }">
		</ng-template>

	</li>
</ul>

When using ngTemplateOutlet, notice that the individual TemplateRef fragments are defined outside of the actual ngFor. Then, within the ngFor, I am using ng-template with the ngTemplateOutlet directive to select the appropriate TemplateRef based on the given type. This is where that Boolean expression evaluation comes into play:

(
	( ( project.type === 'prototype' ) && prototypeRef ) ||
	( ( project.type === 'board' ) && boardRef )
)

In this case, I am using type and strict-equality operator to short-circuit the expression. This means that Angular will use whichever TemplateRef value follows the first matching equality check (which then short-circuits the overall expression).

And, when we run this Angular code, we get the following browser output:

A list of mixed data-types rendered using ngFor and ngTemplateOutlet in Angular 9.0.0-rc.5.

As you can see, both approaches to rendering mixed data-types using ngFor work as expected. But, when we look at the underlying markup, we can see slight differences. The first list has two sets of HTML-Comments to act as placeholders for the two ngSwitchCase directives. And, the second list only has one HTML-Comment to act as a placeholder for the one ngTemplateOutlet directive. This difference is immaterial, really; I'm simply pointing it out to show that the two lists have a slightly different runtime implementation.

Now that I see this on paper, I am not sure how I feel. With such a trivial demo, the first option is clear and concise - the second option feels overly verbose. However, I wonder if this balance would change as the list rendering becomes more complex? The nice thing about the second option is that it separates-out the list rendering from the item rendering. This could become a net-benefit in more complex cases? But, I am not entirely sold.

It's fun to see how dynamic Angular's component templates can be. The platform truly offers us a tremendous amount of flexibility to find the right solution for the right problem. For now, when it comes to rendering a list of mixed data-types, I'll probably stick to my traditional ngSwitch technique. However, as my HTML markup becomes more complex, I will definitely continue to experiment with the ngTemplateOutlet approach to see if it offers any additional clarity.



Reader Comments

I actually think the first example might fair better as the complexity increases, because I could imagine the expression inside:

ngTemplateOutlet

Becoming somewhat difficult to understand.

Reply to this Comment

@Charles,

In terms of "complexity", I was thinking more about the complexity of the item-rendering itself (like, how much markup it takes to render each given item type). But, I think you're probably right. A simple [ngSwitchCase] for each item type is going to be easier to read.

I guess I just needed to see it on paper before I could narrow in on how it would feel.

Reply to this Comment

@NSN,

I am not sure what you mean by "how"? Are you talking about how I write my actual code? Or how it renders on the blog?

Reply to this Comment

@Ben,

In your code, also in YouTube videos, the format of the text is adding more spaces after ( and before ) so (this) looks like ( this ) and it is much much better for the eye to read... but, I didnt find a way to do this with a formatter like Prettier.

Thanks man !

Reply to this Comment

@NSN,

That's just how I write my code :D I don't have a linter or anything. Just muscle-memory :D

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
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.