angular2-component-outlet icon indicating copy to clipboard operation
angular2-component-outlet copied to clipboard

[Suggestion] Add ngInclude functionallity

Open tolemac opened this issue 8 years ago • 7 comments

@laco0416 I'm actually using my own template system in my ng2 apps, it's something like ngInclude from AngularJS. I can declare a template and use it in others places. For example:

<!-- `templateId` directive register the template in a internal container -->
<template templateId="rowTmpl" let-data>
    {{data.name}} {{data.surname}} from {{data.company}}
</template>
<template templateId="detailTmpl" let-data let-last="last">
    Name: {{data.name}} <br/>
    Surname: {{data.surname}} <br/>
    Company: {{data.company}} <br/>
    Age: {{data.age}} <br/>
    Address: {{data.address}} <br *ngIf="last"/>
</template>

<ul>
    <li *ngFor="let item of persons; let index = index; let last = last; let first = first" (click)="toggleSelected(item)">
       <div *ngIf="item.selected">
           <!-- `includeTemplate` is a directive like `ngTemplateOutlet` with extra features -->
           <div *includeTemplate="'detailTmpl', context: item, extraContext: { index: index, last: last, first: first}"></div>
       </div>
       <div *ngIf="!item.selected">
           <div *includeTemplate="'rowTmpl, context: item"></div>
       </div>
    </li>
</ul>

includeTemplate accepts a context object that is used as $implicit context and extraContent that can be used with let-localvar="extraContextField"

The example shows a list of persons with a template if you click on a person it shows the data with another template.

Nowadays you could do it using template ref variables, but the scope of template ref variables is limited, so I use an internal template container.

So far, the new functionallity. But we could go further. We could to have an html file with several templates:

<!-- templates.html -->
<template templateId="tmpl1">...</template>
<template templateId="tmpl2">...</template>
<template templateId="tmpl3">...</template>

Then we could load this html file into a component using angular2-component-outlet:

// AppComponent.ts
const templates = require("./templates.html");
@Component({
    selector: "app",
    templateUrl: "AppComponent.html"
})
export class AppComponent {
    templatesHtml = templates;
    ...
    ...
}
<!-- AppComponent.html -->
<div *componentOutlet="templatesHtml"></div>
<div includeTemplate="tmpl2"></div>

In addition, we could modify conponent-outlet to accept a templateId and extract the template with this templatId from the html and only render the desired template, doing something like:

<!-- AppComponent.html -->
<div *componentOutlet="templatesHtml; templateId: 'tmpl2'"></div>
<div includeTemplate="tmpl2"></div>

I have implemented this and I'm using it in my apps. I'm thinking about public this on github but I not have much time to support people, and I'm thinking to include this to angular2-component-outlet repository.

If you agree, I can do a pull request with the implementation and I can write the documentation too, perhaps I need help with the tests.

Another approach would be to create other repository, something like "ng2Include" with all the features included ' component-outlet'.

What do you think about?

tolemac avatar Oct 14 '16 07:10 tolemac

We could call it ng2-dynamic or ... I don't know! :D

tolemac avatar Oct 14 '16 08:10 tolemac

@tolemac Sorry for late reply. In summary, you think ComponentOutlet can support TemplateRef instead of a string template, right? On that point, I agree with your proposal.

I expect...

// string template style
<div *componentOutlet="template: '...'"></div>

// TemplateRef style
<template #myTmp>
</template>
<div *componentOutlet="templateRef: myTmp"></div>

lacolaco avatar Nov 02 '16 07:11 lacolaco

Yes @laco0416, that would be the begining. But template reference variables has a limited scope:

Looks this:

<div *ngIf="objectType=='human'" id="IfDiv">
  <template #rowTemplate let-obj>
    <span>{{obj.name}} </span>
    <span>{{obj.surname}} </span>
  </template>
</div>
<div *ngIf="objectType=='dog'">
  <template #rowTemplate let-obj>
    <span>{{obj.name}} </span>
    <span>{{obj.dogBreed}} </span>
  </template>
</div>

<div *componentOutlet="templateRef: rowTemplate"></div>

#rowTemplate template is not available outside IfDiv div. And you can have problems using the same ref-var several times. From docs:

Do not define the same variable name more than once in the same template. The runtime value will be unpredictable.

Then you need to build another way to obtain the templates and accept both, templateRef and string.

By the other hand, IMHO, I don't like to mix dynamic component loading and template include on the same component, the implementation of each thing is very different and resulted in ugly code, I have had two things in the same component and it was auful. I think when a component have two behavior so differents is better to have two components.

tolemac avatar Nov 02 '16 08:11 tolemac

@tolemac I think your example code can be rewritten as below:

<template #humanTemplate let-obj>
  <span>{{obj.name}} </span>
  <span>{{obj.surname}} </span>
</template>
<template #dogTemplate let-obj>
  <span>{{obj.name}} </span>
  <span>{{obj.dogBreed}} </span>
</template>

<div *ngIf="objectType=='human'">
  <div *componentOutlet="templateRef: humanTemplate"></div>
</div>
<div *ngIf="objectType=='dog'">
  <div *componentOutlet="templateRef: dogTemplate"></div>
</div>

I think, in this case, simply ComponentOutlet can use TemplateRef. Am I wrong?

lacolaco avatar Nov 06 '16 10:11 lacolaco

Yes @laco0416, you have solved this example, ok. But you can do it using NgTemplateOutlet.

Now suppose you have templates in a separate .html file.

<!-- templates.html -->
<template #tmpl1>...</template>
<template #tmpl2>...</template>
<template #tmpl3>...</template>

How to use them?

tolemac avatar Nov 07 '16 08:11 tolemac

@tolemac Let’s think about these separately. I agree on that ComponentOutlet should be able to take TemplateRef but I don't think it should care about how to pass the TemplateRef (at first step). If you have something pipe to transform templateId to TemplateRef, maybe it can work with componentOutlet.

<div *componentOutlet="templateRef: ('tmpl2' | templateById)"></div>

how do you think about the above?

lacolaco avatar Nov 07 '16 09:11 lacolaco

Yes @laco0416, you could. Then, would you like to mix two features on the same component?

If yes, we have to consider the sense of each options.

When you use dynamic component loading you can use:

<div *componentOutlet="template; context: self; selector:'my-component'"></div>

There are three options:

  • template: html to load.
  • context: object context inner the component.
  • selector: html tag for component.

When you use TemplateRef, the selector option has no sense, neither template. And when you use TemplateRef included through ViewContainer.createEmbeddedView you can set a context and you can use an $implicit context to be use with let-.

Then, I see two very differents uses of componentOutlet component where neither option is common. Neither option is common and the implementation of two usages are very very different. It is for this reason that I do not like to be together.

Repeating me, I think when a component have two behavior so differents is better to have two components.

tolemac avatar Nov 07 '16 12:11 tolemac