TemplateRefs in Angular
Preston Lamb
Posted on May 29, 2019
If you're anything like me, you've heard of TemplateRef
s in Angular, but didn't know what that meant. And it probably scared you off from learning what they are or how they work. But it's really not that scary of a thing to learn. It just takes a little bit of work and some practice, and the benefit can be huge.
I first learned about TemplateRef
s from this article by Isaac Mann. It caught my eye because I'd heard a lot about render props in React, but didn't really know how that applied to Angular. I ended up reading several of Isaac's articles on ng-template
s and TemplateRef
s. They are great articles, and you can find them all here.
Alright, let's get to it. I learned the following things while adding a TemplateRef
component option to the angular-tag-select
package that I manage. Up until now, to use the component meant that you had to use the styles that I had determined. There were a settings that you could pass in on an object that changed some of the styles. Those options included the icon classes, for example, and were very limited. And overwriting any of the default styles was difficult. So I wanted to provide a way for people to get the benefit of the tag select component without being tied down to the default styles.
There are two basic parts when creating a component that uses TemplateRef
s. The first is in the template. The template should contain an element to output the template that the user will see. This could be a div
or some other HTML element, or it can be an <ng-container>
. If you use a an HTML element, that element will show up on the page. If you use <ng-container>
, then there won't be any extra elements on the page. Here's an example template using <ng-container>
:
<ng-container *ngTemplateOutlet="layoutTemplate; context: ctx"></ng-container>
The *ngTemplateOutlet
has two parts: the layoutTemplate
and the context
. The layoutTemplate
is a class variable decorated with the @ContentChild()
decorator from Angular. This is what allows you to pass an <ng-template>
into this component from the parent component. The context
is what allows you to make class variables accessible in the parent component. It's an object with variables and functions that you would like to have access to.
public ctx: any = {
tagsSelectedAtStart: this.tagsSelectedAtStart,
possibleTags: this.possibleTags,
selectedTags: this.selectedTags,
fns: {
toggleTag: this.toggleTag,
}
};
The other important part of implementing a component that uses a TemplateRef
is in the parent component. Let's say that our TemplateRef
component is the <tag-select>
component, and we're implementing this component in our application in a <submit-form>
component. This is the very basics of what the <submit-form>
component template should look like:
<tag-select>
<ng-template>
<!-- HTML to display parts of the tag-select component -->
</ng-template>
</tag-select>
The <tag-select>
component is put on the page, and then the <ng-template>
tag inside there is why and where you can pass in the display elements of the <tag-select>
component. In this case, with this component, you need two basic parts to display: the selected tags and the tags that are possible for selection. However you want to show those, the HTML for it needs to go between the <ng-template>
tag.
Now, do you remember on the <ng-container>
tag we declared the context
variable? We access those variables in the parent in the following way:
<tag-select>
<ng-template let-selected="selected" let-possible="possible">
<!-- HTML to display parts of the tag-select component -->
</ng-template>
</tag-select>
After doing that, we can gain access to the internal class variables. In this case, it gives us access to the list of tags that have been selected and the list of possible tags as well so we can display those two lists however we want. You will very likely have functions exposed on the context as well.
There are a couple things to remember about the context
, if you declare it using a class variable like is seen above, and the functions you expose. The first is that every time one of the internal class variables is updated, the context needs to be explicitly updated as well. If you declare the context
in the following way, you won't need to explicitly update the context any time something changes; Angular will do it for you:
<ng-container *ngTemplateOutlet="layoutTemplate; context: { selected: selected, possible: possible }">
</ng-container>
As for the functions, if you just use the normal syntax for writing a function in a component, you'll run into issues with undefined
variables. To get around that, use arrow functions. The following pictures will demonstrate.
toggleTag = (tag: Tag) => {
// Do Stuff
this.updateContext('selected', this.selected)
}
updateContext(key: string, value: any) {
this.ctx[key] = value;
}
I know this all seems very complicated, but once i jumped in and started trying it out it wasn't too bad. I still have a lot of practice and a long ways to go, but I'm excited about the possibilities that this will open for me as an Angular developer. The ability to use or provide packages that do the complex logic for the end user, but allow them to determine the look and feel is really powerful. It's why this pattern has become so popular in React.
Posted on May 29, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.