Using TemplateRef and ngTemplateOutlet To Pass Templates Between Components In Angular

I came across an interesting challenge recently that involved being able to pass a “template” between components. In my particular case, I had a “table” component that simply laid out tabular data, nothing too crazy. In most cases I simply wanted the text value of a property to be displayed, but in some rare cases, I needed a custom template to be shown. While it may seem simple, simply pass through a string variable with HTML inside and be done with it, when it comes to data binding, it actually becomes a bit more complex than that.

I quickly came across TemplateRef in the Angular documentation which sounded like what I needed, but as is the case with many Angular internals, the documentation was somewhat lacking. So let’s explore TemplateRef and see what it can actually do.

The Basics

The basics to actually get a “TemplateRef” variable in the first place looks like so. Let’s say I create a component called “ParentComponent”. I’m going to create the HTML (thus far) looking like so :

<ng-template #myTemplate>
    <h1>This is my Template</h1>
</ng-template>

All this is doing is creating a “template” object called #myTemplate. This actually doesn’t render anything to the page, it simply tells Angular that at some point, you will use this template somewhere. I think it’s probably pretty similar to Handlebars templating if you’ve ever used that before.

Now the code behind our component will be very simple.

export class ParentComponent implements OnInit {

  @ViewChild('myTemplate', {static : true}) myTemplate : TemplateRef;

  myModel = {
  };

  constructor() { }

  ngOnInit() {
    this.myModel =  {
      template : this.myTemplate
    }
  }

}

So all we are doing here is using ViewChild to get a reference to the actual template and storing it in a TemplateRef variable. I’m also creating a “model” object. Although you don’t have to do this, from my MVC days I prefer creating a “ViewModel” to pass between components. In our case we want to create a second component to accept this template.

So I’m going to create a component called ChildComponent. The code behind the component looks like so :

export class ChildComponent implements OnInit {
  
  @Input() model : any;

  constructor() { }

  ngOnInit() {
  }

}

It will accept an input of “any” which in our case will be our model we created in our ParentComponent. Really I should create a proper strongly typed class here but for now, the type of any will suffice.

For the HTML of our child component, we then need to do the following :

This is my child component. <br />

<ng-container *ngTemplateOutlet="model.template"></ng-container>

What we are doing here is outputting an ng-container that is given a template (The template we passed through) to render.

If we go back to our ParentComponent and add the child tag to it so the HTML ends up looking like :

<ng-template #myTemplate>
    <h1>This is my Template</h1>
</ng-template>

<app-child [model]="myModel"></app-child>

Run everything and wham! Our template is passed through to our child component that renders it perfectly!

Why Not Use NG-Content?

So another way to achieve this is using the <ng-content> tag which we will definitely talk about in another post. But if a developer is asking you why not just use that? Well… After using it for a while I’ve found that the ng-content tag is great for single projections, but if you are wanting to pass multiple templates into a component ng-content is a little bit more rough around the edges on how that works. The other thing I found was that the data binding was no where near as intuitive in ng-content as it is using ngTemplateOutlet.

Databinding?! You can do that?! You sure can. Let’s take a look at hot it works.

Databinding NgTemplateOutlet

A very common scenario for templates is that you will want to databind values inside the actual template. The issue with this is, what is it binding to? In our example above, if we added data bindings to our template would it be binding to the ParentComponent since that’s where the template is defined, or would it bind to the ChildComponent because that’s where the template is actually output?

The answer is neither. It doesn’t bind at all unless you specify where it should bind and to what.

Let’s modify our template to bind a subheading. The way to make this work looks like :

<ng-template #myTemplate let-data="data">
    <h1>This is my Template</h1>
    <h4>{{data.subheading}}</h4>
</ng-template>

So you’ll notice we do a “let” command above. What that says is, I’m going to be passed a value called “data” (That’s the data actually inside the quotes), when I’m given that, bind it to a variable called “data” that is scoped to this template.

Then below, I will expect on this data object to be a subheading property.

Go back to our ParentComponent and change how we bind the model to also have a subheading property :

ngOnInit() {
  this.myModel =  {
    template : this.myTemplate, 
    subheading : 'this is my subheading'
  }
}

Because we are still passing through our model to the child component, the binding and codebehind there stays the same. But inside the actual HTML of the ChildComponent we have to tell it the context we want to give it. We do that like so :

This is my child component. <br />
<ng-container *ngTemplateOutlet="model.template; context: { data : model}"></ng-container>

So notice on the *ngTemplateOutlet, we pass in a context. Now personally, I like to create another “sub” object with a key value like this. This means that you can pass in multiple values on the same object etc, but it’s up to you.

Now when our template is run, it knows about this object called “data”, and can use that to bind values. Super easy!

1 comment

Leave a Reply

Your email address will not be published. Required fields are marked *