Skip to main content
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Jamie Krug and Simon Free
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Jamie Krug ( @jamiekrug ) Simon Free ( @simonfree )

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

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

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

Reader Comments

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

15,663 Comments

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

15,663 Comments

@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?

11 Comments

@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 !

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