How to use templateRef?

Kris Hollenbeck picture Kris Hollenbeck · Mar 28, 2016 · Viewed 63.9k times · Source

I am trying to find a way to dynamically construct a template in Angular2. I was thinking templateRef might provide a way to do this. But I could be wrong.

I found an example of templateRef being used here.

I was looking at templateRef in this example. I noticed the syntax is [ng-for-template] I also tried [ngForTemplate] cause I know this has changed recently.

So at the moment I have this:

import {Component, TemplateRef} from 'angular2/core';

@Component({
    selector : 'body',
    template : `
        <template [ngForTemplate]="container">
            <div class="container"></div>
        </template>
    `
})

export class App
{
    @ContentChild(TemplateRef) container;

    constructor() {}

    ngAfterContentInit()
    {
        console.log(this);
    }
}

This example throws an error:

Can't bind to 'ngForTemplate' since it isn't a known native property

So firstly I am wondering. What is the right way to do this? The docs don't provide any examples.

Secondly, is there a good way I can add new template logic to my template or dynamically construct a template? The structure of the application can be a very large amount of different structural combinations. So if possible I would like to see if there is a way I can do this without having a huge template with a bunch of different ngIf and ngSwitch statements..

My question is really the first part about templateRef. But any help or suggestions on the second part is appreciated.

Answer

Eric Martinez picture Eric Martinez · Mar 28, 2016

Creating your own template directive it's not difficult, you have to understand two main things

  • TemplateRef contains what's inside your <template> tag
  • ViewContainerRef as commented by Gunter, holds the template's view and will let you to embed what's inside the template into the view itself.

I will use an example I have when I tried to solve this issue, my approach is not the best for that, but it will work for explaining how it works.

I want to clarify too that you can use any attribute for your templates, even if they're already used by builtin directives (obviously this is not a good idea, but you can do it).

Consider my approach for ngIfIn (my poor approach)

<template  [ngIfValue]="'make'" [ngIfIn]="obj">
  This will print
</template>
<template [ngIfValue]="'notExistingValue'" [ngIfIn]="obj">
  This won't print
</template>

We have here two templates using two inputs each ngIfIn and ngIfValue, so I need my directive to grab the template by these two inputs and get their values too, so it would look like this

@Directive({
  selector : '[ngIfIn][ngIfValue]',
  inputs : ['ngIfIn', 'ngIfValue']
})

First I need to inject the two classes I mentioned above

constructor(private _vr: ViewContainerRef, private _tr: TemplateRef) {}

I also need to cache the values I'm passing through the inputs

  _value: any;
  _obj: any;

  // Value passed through <template [ngIfValue]="'...'">
  set ngIfValue(value: any) {
    this._value = value;
  }

  // Value passed through <template [ngIfIn]="...">
  set ngIfIn(obj: any) {
    this._obj = obj;
  }

In my case I depend on these two values, I could have my logic in ngOnInit but that would run once and wouldn't listen for changes in any of the inputs, so I put the logic in ngOnChanges. Remember that ngOnChanges is called right after the data-bound properties have been checked and before view and content children are checked if at least one of them has changed (copy and paste from the docs).

Now I basically copy & paste NgIf logic (not so complex, but similar)

  // ngOnChanges so this gets re-evaluated when one of the inputs change its value
  ngOnChanges(changes) {
    if(this._value in this._obj) {

      // If the condition is true, we embed our template content (TemplateRef) into the view
      this._vr.createEmbeddedView(this._tr);
    } else {

      // If the condition is false we remove the content of the view
      this._vr.clear();
    }
  }

As you see it's not that complicated : Grab a TemplateRef, grab a ViewContainerRef, do some logic and embed the TemplateRef in the view using ViewContainerRef.

Hopefully I made myself clear and I made how to use them clear enough also. Here's a plnkr with the example I explained.