23

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.

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
khollenbeck
  • 16,028
  • 18
  • 66
  • 101
  • The code you're trying to do is the [NgFor](https://github.com/angular/angular/blob/6de68e2f1f2b8403266e94f13f1986dfd09e5969/modules/angular2/src/common/directives/ng_for.ts#L90) one. You'd have to create your own directive that grabs your template by `[ngForTemplate]`. Your second question seems to be related to the first one, but it's a little bit too broad. Making your own template it's not difficult at all. – Eric Martinez Mar 28 '16 at 16:40
  • @EricMartinez, Yeah the second part is a bit broad. Mostly I am just trying to figure out how templateRef is meant to be used. I couldn't find a good example. – khollenbeck Mar 28 '16 at 16:50
  • There is an example in the docs about how to use TemplateRef. It is in the dev guide docs, not the API docs: https://angular.io/docs/ts/latest/guide/structural-directives.html#!#unless – Mark Rajcok Apr 14 '16 at 00:22
  • @MarkRajcok, Good to know. Thanks for the info. – khollenbeck Apr 14 '16 at 13:38

2 Answers2

27

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.

Deilan
  • 4,740
  • 3
  • 39
  • 52
Eric Martinez
  • 31,277
  • 9
  • 92
  • 91
7

ngForTemplate is only supported with ngFor

<template [ngFor] [ngForOf]="..." [ngForTemplate]="container"

or

<div *ngFor="..." [ngForTemplate]="container"

not on a plain template. It is an @Input() on the NgFor directive

Another way to use TemplateRef

If you have a reference to ViewContainerRef you can use it to "stamp" the template

constructor(private _viewContainer: ViewContainerRef) { }

ngOnInit() {
  this.childView = this._viewContainer.createEmbeddedView(this.templ);
  this.childView.setLocal('data', this.data);
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Okay I am still a bit confused. Can I get a reference to the template in my current component? Or does it only give reference to an external directive? – khollenbeck Mar 28 '16 at 17:21
  • 1
    If you have `constructor(private templ:TemplateRef)` you get a reference to the first ` – Günter Zöchbauer Mar 28 '16 at 17:25
  • Yeah that mostly makes sense. I will take a moment to try out your code. – khollenbeck Mar 28 '16 at 17:40
  • Do you happen to have an example of this? I am not sure what `this.templ` should be? Is that suppose to be templateRef? – khollenbeck Mar 28 '16 at 17:52
  • 1
    `this.templ` would be a `TemplateRef` instance (got by one of the methods mentioned above) - for example `itemTemplate` in https://github.com/dsebastien/angularConnect2015London/blob/master/14%20Better%20concepts%20less%20code%20in%20Angular%202.md. A few related links I collected: http://plnkr.co/edit/fwd1kh9TXfemagpuDnLZ?p=preview, https://github.com/angular/angular/issues/6310, http://plnkr.co/edit/LK1Aa3vhawd2sMl0Wj6F, https://github.com/angular/angular/issues/7443, https://docs.google.com/document/d/19_9pshmkAQOA67UWTm41bzWbvikwerVjnCD97D0JS7g/edit – Günter Zöchbauer Mar 28 '16 at 17:58
  • @GünterZöchbauer - could you please help me with this issue with TemplateRef - https://stackoverflow.com/questions/51739779/how-to-manipulate-dom-elements-inside-a-templateref-without-using-pure-javascrip?noredirect=1#comment90462167_51739779 – Manu Chadha Aug 08 '18 at 18:32
  • I saw an another answer from you which uses `$implicit`. That seem to work but I haven't finished testing yet – Manu Chadha Aug 08 '18 at 18:50