Lihai
Lihai

Reputation: 323

How to render templates dynamically in Angular 2?

Im trying to select templates dynamically in a view simply by referencing them with the traditional "#" character of Angular 2. In my project i handle errors and display them to a user, I have a dialog component which should have its content html based dynamically injected, so thats why im using templates.

I read some articles which shows a way of how to do it when i already know the name of the template reference in my case i dont know the name of the reference i get the name on runtime. I followed this guide in particular: https://www.bennadel.com/blog/3101-experimenting-with-dynamic-template-rendering-in-angular-2-rc-1.htm

So currently my dialog component has the following view:

<template #err_1 let-property1="p1" let-property2="p2">
property1: {{p1}}
property2: {{p2}}

</template>

<template #err_2 let-property1="p1" let-property2="p2">
<p *ngIf="p1">{{p1}}</p>
property2: {{p2}}
</template>

<!--The code for the template directive i took from the guide in the link above-->
<tem [render]="templateRef"
     [context]="context">
</tem>

In my dialog.ts I have the following code:

@Component({
  selector: 'error-dialog',
  queries: {
    templateRef: new ViewChild("err_1")
  },
  templateUrl: './dialog.html'
})
...

"TemplateRendererDirective" directive source i took from their guide from the link above. pay attention that what confused me: templateRef basically gets an object of: "ViewChild" even though the directive eventually gets TemplateRef instance, how this is possible?

so only if i know which error template i want to render for example: "err_1" i just referencing it beforehand in dialog.ts, but its not the case, i want dynamically tell i want to render "err_1", "err_2" etc.. and give the context (which is the object to fill that template with data - p1, p2 for the example, also dynamically)

Is it possible to do it?

Upvotes: 3

Views: 4413

Answers (1)

yurzui
yurzui

Reputation: 214007

As i mentioned in comments you can try to use @ViewChildren to do it working. But i use additional directive TemplateNameDirective to manipulate template's name.

Here's how it looks:

@Directive({
  'selector': 'template[name]'
})
export class TemplateNameDirective {
  @Input() name: string;
  constructor(public templateRef: TemplateRef<any>) {}
}

@Component({
  selector: 'error-dialog',
  template: `
    <template name="err_1" let-item>
      property1: {{item.p1}}
      property2: {{item.p2}}
    </template>

    <template name="err_2" let-item>
      <p *ngIf="item.p1">{{item.p1}}</p>
      property2: {{item.p2}}
    </template>

    <!-- 
       I use ngTemplateOutlet directive 
       https://angular.io/docs/ts/latest/api/common/index/NgTemplateOutlet-directive.html 
    -->
    <template [ngTemplateOutlet]="templateRef" [ngOutletContext]="{ $implicit: context }">
    </template>
  `
})
export class ErrorDialogComponent {
  @ViewChildren(TemplateNameDirective) children: QueryList<TemplateNameDirective>;

  templateRef: TemplateRef<any>;
  context: any;

  public setContent(errTemplateName, context) {
    this.templateRef = this.children.toArray()
      .find(x => x.name === errTemplateName).templateRef; 
    this.context = context;
  }
}

Parent view:

<error-dialog #dialogRef></error-dialog>
<button (click)="dialogRef.setContent('err_1', { p1: 'test'})">Test err_1</button>
<button (click)="dialogRef.setContent('err_2', { p2: 'test2'})">Test err_2</button>

Plunker Example

Notice: i am passing ngOutletContext like object with $implicit property.

using the key $implicit in the context object will set it's value as default.

It works as follows:

[ngOutletContext]="{ $implicit: row }"  ==> <template let-row>

[ngOutletContext]="{ item: row }"       ==> <template let-row="item">

Upvotes: 5

Related Questions