Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Clark Valberg and David Fraga
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Clark Valberg ( @clarkvalberg ) David Fraga ( @davidfraga )

Playing With Recursive Ng-Template References In Angular 6.1.10

By on

Yesterday, I started to think about recursive Angular layouts for the first time. And, as someone who has bought into the "everything is a component" point-of-view, my natural instinct was to reach for recursive components. But then, I remembered that Ng-Templates allow for some very dynamic rendering possibilities. As such, before I went the component route, I wanted to see what kind of recursive behavior I could squeeze out of a single component with an embedded template reference.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

The ng-template directive, in Angular, provides two basic behaviors:

  • A way to define an Angular template.
  • A way to render an existing template reference.

Because of this duality, we can theoretically use ng-template to define an Angular view-partial that uses ng-template to render itself again, recursively (more or less). To experiment with this idea, I created an app component that initializes a tree-like data structure:

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

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

interface Tree {
	root: TreeNode;
}

interface TreeNode {
	label: string;
	children: TreeNode[];
}

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

	public data: Tree;
	public selectedTreeNode: TreeNode | null;

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

		this.selectedTreeNode = null;
		this.data = {
			root: {
				label: "first",
				children: [
					{
						label: "second-a",
						children: [
							{
								label: "third-first",
								children: [
									{
										label: "ferth",
										children: [
											{
												label: "fiver",
												children: []
											}
										]
									}
								]
							}
						]
					},
					{
						label: "second-b",
						children: [
							{
								label: "third",
								children: []
							}
						]
					}
				]
			}
		}

	}

	// ---
	// PUBLIC METHODS.
	// ---

	// I select the given tree node, and log it to the console.
	public selectNode( node: TreeNode ) : void {

		this.selectedTreeNode = node;

		console.group( "Selected Tree Node" );
		console.log( "Label:", node.label );
		console.log( "Children:", node.children.length );
		console.groupEnd();

	}

}

As you can see, my Tree is an aggregation of nested TreeNodes. I am also defining a selectNode() method so that we can see how a recursive template can still hook into the public context of the component view.

I came up with two approaches to the recursive ng-template reference. In the first approach, I am using the ngFor directive, which allows you to pass-in a TemplateRef in lieu of in-line markup:

<!-- Define the recursive template. -->
<ng-template #nodeTemplateRef let-node>

	<div class="node" [class.node--selected]="( node === selectedTreeNode )">

		<a (click)="selectNode( node )" class="node__label">
			{{ node.label }}
		</a>

		<div *ngIf="node.children.length" class="node__children">

			<!-- Invoke the recursive template. -->
			<ng-template
				ngFor
				[ngForOf]="node.children"
				[ngForTemplate]="nodeTemplateRef">
				<!--
					NOTE: The "$implicit" property of the ngFor context is what will
					be made available to the template ref's implicit let-node binding.
				-->
			</ng-template>

		</div>

	</div>

</ng-template>

<!--
	Initiate the recursive template rendering. Because our recursive template is going to
	be using the ngFor directive to render recursively, the "context" in the recursive
	instances is going to be the ngForContext. As such, we have to "mock" the initial
	context to look like the context that the ngFor directive will expose internally.

	NOTE: If we used ngContainer or ngTemplate to invoke the recursion internally, we'd
	have more control over which values were made available at each level.
-->
<ng-template
	[ngTemplateOutlet]="nodeTemplateRef"
	[ngTemplateOutletContext]="{ $implicit: data.root }">
</ng-template>

<p class="note">
	<em>Ng-For Template Rendering</em>
</p>

As you can see, in this approach, I have an initial ng-template directive that assigns the TemplateRef to a view-local variable, "nodeTemplateRef". This template renders a given node and its children. In order to render the children, I am using the ngFor directive; and, I'm passing in the nodeTemplateRef value as the ngFor template (to be used as the collection is unrolled).

I then use another ng-template instance to kick-off the recursive ng-template execution. And, when we run this in the browser and click on a few of the rendered nodes, we get the following output:

We can use ng-template and ng-for to recrusively render a view partial in Angular 6.1.10.

As you can see, the ngFor directives was successfully able to recursively render the ngTemplate in which it was defined.

When using the ngFor directive, there's no way to explicitly pass-in a "context" object - the ngFor directive implicitly passes-in the ngForContext object as the template context. This means that my ng-template directive has to use the "implicit" export of the ngFor context as the "let-node" template binding.

In this case, that's not an issue since I only want to pass-in the one value. But, for more flexibility, we can forgo the ngFor directive and simply use another ng-template to render the template recursively (in much the same way that we use an ng-template instance to initiate the recursion):

<!-- Define the recursive template. -->
<ng-template #nodeTemplateRef let-node="node">

	<div class="node" [class.node--selected]="( node === selectedTreeNode )">

		<a (click)="selectNode( node )" class="node__label">
			{{ node.label }}
		</a>

		<div *ngIf="node.children.length" class="node__children">

			<ng-template ngFor let-child [ngForOf]="node.children">

				<!-- Invoke the recursive template. -->
				<ng-template
					[ngTemplateOutlet]="nodeTemplateRef"
					[ngTemplateOutletContext]="{ node: child }">
					<!--
						Because we are using nested ngTemplates (rather than the template
						input of the ngFor directive), we have more control over how the
						data is made available to the recursive template. In this case,
						we're passing "child" through as "node".
					-->
				</ng-template>

			</ng-template>

		</div>

	</div>

</ng-template>

<!-- Initiate the recursive template rendering. -->
<ng-template
	[ngTemplateOutlet]="nodeTemplateRef"
	[ngTemplateOutletContext]="{ node: data.root }">
</ng-template>

<p class="note">
	<em>(alternative) Ng-Template Template Rendering</em>
</p>

As you can see, this time, instead of relying on ngFor to implicitly export the loop item, I'm using the ng-template's ngTemplateOutletContext property to explicitly define the context for the recursive template. And, when we run this in the browser and click on a few of the rendered nodes, we get the following output:

We can use ng-template to recrusively render an ng-template view partial in Angular 6.1.10.

As you can see, we were able to use the ng-template to:

  • Define the recursive template.
  • Initiate the recursive template execution.
  • Render the template reference recursively.

This approach leads to a bit more mark-up (both in the component view and in the rendered Document Object Model), when compared to the ngFor approach; but, you can see that it provides for a bit more flexibility as you can control the shape of the ng-template context object.

I don't often have to reach for the ng-template directive in order to build an Angular 6 application. But, it is clear that the ng-template directive (and other directives that accept a TemplateRef) afford a great deal of power in view rendering. In this case, we were able to use the ng-template directive to traverse a recursive data-structure within the bounds of a single component.

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

Reader Comments

1 Comments

Hey Ben, Actually I'm a new guy to angular and I found this is very helpful. I want to know how to customize this tree structure by adding collapse and expand toggle icons(like +, -) at each level, it'd be great if you can help with some code explanation with comments same as you did above.

15,674 Comments

@Munindar,

Yes, that should be totally doable. Though, it would likely be easier with recursive components than with a recursive template since the component would be able to store local state outside of tree-structure. I'll see if I can put together a demo for this.

Note: You could do this with the template approach; but, you would have to store the toggle-state in the tree structure itself.

15,674 Comments

@Munindar,

Good sir, I tried to explore your question with a new post:

www.bennadel.com/blog/3601-more-fun-with-recursive-components-tree-state-and-one-way-data-flow-in-angular-7-2-13.htm

It's an arbitrarily nested Folder structure in which you can expand / collapse each or all folders. And, to make it a bit more exciting, I'm persisting the expanded view-model the Browser URL so that the Folder Tree will maintain state across page refreshes. I hope this helps.

Recursion is just a fun topic to explore!

1 Comments

How to I access record object inside ng-template elseblock
<ng-template #cellTemplate let-col="column" let-record="record">

        <input
          id="eldmLobDomainAlias_{{ record.id }}"
          name="domainAlias"
          #domainId
          type="search"
          [ngModel]  *ngIf="record.alias === record.name; then thenBlock ; else elseBlock"
          (blur)="this.changeAction(record)"
          maxlength="25"
          (search)="this.utilsComponent.addOrEditAlias(record, domainId.value)"
          class="domainAliasInput tk-input-masking ng-pristine ng-valid ng-touched form-control"

        />
        <ng-template #elseBlock>"diff"</ng-template>
        <ng-template #thenBlock>"Same"</ng-template>
      </ng-template>
15,674 Comments

@Arul,

Hmmm, I am surprised it isn't available. As long as the Else and Then ng-template instances are declared inside of the top-level ng-template, I would have assumed that the let-record should be made available. I'll have to try this out as the behavior is then surprising.

1 Comments

Ho to get the index for nested arrat loop. i want to display like
1.
1.1
1.2
2
2.1
2.2
2.2.1
2.2.2
3
3.1

Ho we can display index like this.?

1 Comments

Hey thanks for the nice breakdown, I got this working but found it tried to render an empty instance of the repeater, and got undefined for "node". I was able to fix this by putting an *ngIf="node" on the first child of the parent template. Curious if you have any insight as to why this occurs, I thought it might be my data but it happens even if I use your mock.

15,674 Comments

@Adam,

Oh, that's interesting. Of a repeater is empty, it seems like the ngFor wouldn't run (since the collection is empty). It could be a bug in Angular; or in my code. Since you say you can cause the issue with the demo, can you share the this.data you were using at the time?

15,674 Comments

@Chethan,

Did you get it figured out? That's a bit tricky for something recursive, especially in this case where it's all a single template, so you lack the benefit of having a Function scope to isolate a given index. That said, you should be able to get the index by changing this:

<ng-template #nodeTemplateRef let-node>

to:

<ng-template #nodeTemplateRef let-node let-index="index">

I think this would give you access to index within the template; but, I am not sure how you reference the index of parent iterations.

You may be better just trying to use ul and li and have the DOM generate the list-item numbers for you. Though, to be honest, I don't know off-hand how nested numeric list-items work.

15,674 Comments

@Charles,

One of the things I appreciate most about digging into the Material Design library is to see how they use nested components. In my work, I don't often use nested components - usually just one component with inputs. But, there's a lot of flexibility that comes with nested components that I am still trying to wrap my head around and learn from.

15,674 Comments

@Sunil, @Ju,

Very cool - glad you are finding this helpful. Though, I will admit that it's a bit tricky at first glance; I would suggest looking into recursive components instead of recursive templates. It's the same principles; but, with a little more intuitive model, I think. If you look up in the comments, I have an example of this.

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