How to use templateRef?

2020-05-16 01:30发布

问题:

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.

回答1:

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.



回答2:

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);
}