Angular pass multiple templates to Component

Daniel Grima picture Daniel Grima · Apr 26, 2017 · Viewed 16.3k times · Source

I'm trying to create a component that accepts multiple templates as inputs. This is the example I have:

@Component({
    selector: 'data-list',
    styles: [
        require('./data-list.component.scss')
    ],
    template: `
        <ng-template
            *ngFor="let item of itemsData"
            ngFor let-item [ngForOf]="[item]" [ngForTemplate]="itemTemplate"
        ></ng-template>
    `
})

export class DataListComponent {
    @Input() itemsData: any[];
    @ContentChild(TemplateRef) itemTemplate: TemplateRef<ElementRef>;
}

As you can see it's a fairly simple component that I'm trying out. This component simply accepts the data of the items to be displayed as well as the template of the item. This component can be used like so:

<data-list [itemsData]="data">
    <ng-template let-item>
        <h1>{{ item.header }}</h1>
        <div>{{ item.content }}</div>
    </ng-template>
</data-list>

As shown above I'm passing the template using ng-content which is then read by the DataListComponent with @ContentChild(TemplateRef) itemTemplate: TemplateRef<ElementRef>;.

My question is whether it is possible to pass multiple templates to a component.

As an example one would pass the template for the items, but a different template is needed in case it's the first item. This would mean that the check of the first item would be made in the DataListComponent but then use a template specified by the component using it.

Simple example:

enter image description here

I can do something like so to cater for this:

@Component({
    selector: 'data-list',
    styles: [
        require('./data-list.component.scss')
    ],
    template: `
        <span *ngFor="let item of itemsData; let i = index" >
            <ng-template *ngIf="i > 0; else nextTmpl"
                ngFor let-item [ngForOf]="[item]" [ngForTemplate]="itemTemplate"
            ></ng-template>
        </span>
        <ng-template #nextTmpl>
            Next
        </ng-template>
    `
})

However like so the "Next Template" is not specified by the component using the DataListComponent and therefore will always be the same template.

Answer

aleroy picture aleroy · Sep 14, 2017

I solved this same issue by using the string selector available to the ContentChild decorator.

You will need to specify template variables when using your data-list component:

<data-list [itemsData]="data">
    <ng-template #firstItemTemplate let-item>
        <h1 style="color:red;">{{ item.header }}</h1>
        <div>{{ item.content }}</div>
    </ng-template>
    <ng-template #standardTemplate let-item>
        <h1>{{ item.header }}</h1>
        <div>{{ item.content }}</div>
    </ng-template>
</data-list>

Then, inside your data-list component class, assign the template variables to local variables on the component:

@Input() itemsData: any[];
@ContentChild('firstItemTemplate') firstItemTemplate: TemplateRef<ElementRef>;
@ContentChild('standardTemplate') standardTemplate: TemplateRef<ElementRef>;

After this, you'll be able to render the passed-in templates from your data-list component.

@Component({
    selector: 'data-list',
    styles: [
        require('./data-list.component.scss')
    ],
    template: `
        <span *ngFor="let item of itemsData; let i = index" >
            <ng-template *ngIf="i == 0; else nextTmpl"
                ngFor let-item [ngForOf]="[item]" [ngForTemplate]="firstItemTemplate"
            ></ng-template>
            <ng-template #nextTmpl 
                ngFor let-item [ngForOf]="[item]" [ngForTemplate]="standardTemplate"
            ></ng-template>
        </span>
    `
})