3

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?

yurzui
  • 205,937
  • 32
  • 433
  • 399
Lihai
  • 323
  • 2
  • 6
  • 18
  • What do you exactly want? Change `templateRef` param? – yurzui Oct 30 '16 at 05:27
  • Dynamically set templateRef param not when I declare the dialog component like I showed in the post. But how can I create TemplateRef from ViewChild object? I need templateRef object because the function createEmbededView that inject the content to the view expect to get templateRef – Lihai Oct 30 '16 at 05:31
  • `ViewChild` grabs `TemplateRef` automatically if your `err_1` hash relates to ` – yurzui Oct 30 '16 at 05:41
  • It relates to template element so how can I get the templateRef object from ViewChild? when I try simply casting the typescript compiler gives me an error – Lihai Oct 30 '16 at 05:43
  • Add please code how it looks – yurzui Oct 30 '16 at 05:44
  • And if you read comments on this article then you aware that there is `ngTemplateOutlet` directive wich does the same as `TemplateRendererDirective` https://www.bennadel.com/blog/3102-templates-appear-to-maintain-lexical-bindings-in-angular-2-rc-1.htm – yurzui Oct 30 '16 at 05:49
  • This also will help you to understand how `ViewChild` works http://stackoverflow.com/questions/39908967/how-to-get-reference-of-the-component-associated-with-elementref-in-angular-2/39909203#39909203 – yurzui Oct 30 '16 at 05:51
  • setContent(errTemplateName, context) {this.templateRef=new ViewChild(errTemplateName); this.context=context } if I create this function to set templateRef parameter dynamically I get compilation error. Why this is not working and in queries attribute it works? – Lihai Oct 30 '16 at 05:51
  • I think you can't create it this way. Angular2. `new ViewChild` just creates property decorator. You can try to use `@ViewChildRen(TemplateRef)` – yurzui Oct 30 '16 at 06:01

1 Answers1

5

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">
yurzui
  • 205,937
  • 32
  • 433
  • 399
  • Unfortunately it doesnt work, this.children is undefined for some reason, i put "name" attribute in template element but it doesnt work for me. – Lihai Oct 30 '16 at 19:02
  • You have to add `TemplateNameDirective ` to `declarations` array of '@NgModule` – yurzui Oct 30 '16 at 19:13
  • Can you reproduce it on a plunker? – yurzui Oct 30 '16 at 19:18
  • Your example works as a self independent component, but im using Material 2 dialog component as the guide shows here: https://github.com/angular/material2/tree/master/src/lib/dialog as u can see I open the dialog and gives the name of the component as an argument when i do so, children property remains undefined for a reason. – Lihai Oct 30 '16 at 20:21
  • Can you improve this plunker https://plnkr.co/edit/6o8UrUwfLIEHBUa3IaBB?p=preview to reproduce your problem? – yurzui Oct 30 '16 at 20:50
  • here you go: https://embed.plnkr.co/4OJG4Id6FC0xk67q70iJ/ the Error is: Cannot read property 'toArray' of undefined on 'setContent' function. – Lihai Oct 30 '16 at 22:18
  • Thank's for the plunker. That's because `setContent` method is running before `ngAfterViewInit` hook. I updated plunker https://embed.plnkr.co/BZMj4HtHnwaobpaua9Zk/ Notice: i am using `ngZone` to wait until popup is loading. In my answer the `error-dialog` component is already loaded – yurzui Oct 31 '16 at 06:33