Angular 2 use a template for ng-content to use inside component loop
Solved this with the following:
Component template usage:
<search-field>
<ng-template let-item>
<span><strong>{{item.username}}</strong></span>
<span>{{item.email}}</span>
</ng-template>
</search-field>
Component template definition:
<div class="search-container">
<div class="search-input">
<input type="text" class="form-control" placeholder="Search users..." [(ngModel)]="searchString" (ngModelChange)="searchStringChanged($event)">
<div class="md-icon">search</div>
</div>
<ul class="search-results" *ngIf="searchResults.length > 0">
<li class="search-results__item" *ngFor="let item of searchResults">
<ng-template [ngTemplateOutlet]="templateRef" [ngTemplateOutletContext]="{$implicit: item}"></ng-template>
</li>
</ul>
</div>
Component class:
@Component({...})
export class SearchFieldComponent {
@ContentChild(TemplateRef) templateRef: TemplateRef<any>;
// ...
}
The explanation:
Using ng-template
, I can use the let-item
syntax, where item
is the data that will be passed into the template on each iteration of the loop.
And in order to make the above possible, in the search-field
component I use ng-template
with ngTemplateOutlet
as the template reference, and ngTemplateOutletContext
is given the value {$implicit: item}
, where item
is the data I want to pass into the template.
Lastly, in the component class I need to use ContentChild
to get the reference to the template to use in the ngTemplateOutlet
.
ng-content in for loop
You can take a look at how angular material implemented tabs control.
There are some caveats about this approach https://github.com/angular/angular/issues/18691 but anyway here is simplified version:
tab.component.ts
@Component({
selector: 'app-tab',
template: `
<ng-template>
<ng-content></ng-content>
</ng-template>`
})
export class TabComponent {
...
@ViewChild(TemplateRef) template: TemplateRef<any>;
}
tabs.component.ts
<div *ngFor="let tab of tabs; let i = index"
class="tab-pane" [ngClass]="{'active': i === 0}"...>
<ng-container *ngTemplateOutlet="tab.template"></ng-container>
</div>
Plunker Example
How to loop through section elements to project with ng-content
I would create a directive like:
@Directive({
selector: '[stepper-section]'
})
export class StepperSectionDirective {}
then add stepper-section
attribute to each of sections:
<stepper>
<section stepper-section>content here<section>
<section stepper-section>content here<section>
<section stepper-section>content here<section>
</stepper>
and finally make use of @ContentChildren
decorator to query all the sections:
@ContentChildren(StepperSectionDirective) sections: QueryList<StepperSectionDirective>;
Ng-run Example
If you want to loop through content and render it dynamically you can wrap your children with ng-template
and use ngTemplateOutlet
directive to render them in StepperComponent:
html
<app-stepper>
<ng-template stepper-section>Content 1</ng-template>
<ng-template stepper-section>Content 2</ng-template>
<ng-template stepper-section>Content 3</ng-template>
</app-stepper>
stepper-section.directive.ts
@Directive({
selector: '[stepper-section]'
})
export class StepperSectionDirective {
hidden = false;
constructor(public templateRef: TemplateRef<any>) {}
}
stepper.component.ts
@ContentChildren(StepperSectionDirective) sectionDirs: QueryList<StepperSectionDirective>;
stepper.component.html
<button *ngFor="let section of sections; index as i;"
[class.enabled]="activeSection === i" (click)="goTo(i)">
Step {{ i + 1 }}
</button>
<div class="content">
<ng-container *ngFor="let section of sections">
<ng-template *ngIf="!section.hidden"
[ngTemplateOutlet]="section.templateRef"></ng-template>
</ng-container>
</div>
Ng-run Example
The difference between these two approaches is that in the first case all child content gets rendered and we only manipulate display: none
to those steps we want to hide.
In the second approach we have more control on what we want to render and we render only one step at specific time.
How to render multiple ng-content inside an ngFor loop using Angular 4?
The best option in your case would be trancluding ng-template
by using ngTemplateOutlet
like:
<app-table [data]="myData">
<ng-template #lineHeader let-line>
<div>
{{line.name}}
</div>
</ng-template>
<ng-template #lineContent let-element>
<div>
{{element.name}}
</div>
</ng-template>
</app-table>
app-table.component.ts
@Component({
selector: 'app-table',
template: `
<div *ngFor="let line of data">
<ng-container *ngTemplateOutlet="lineHeaderTmpl,
context: { $implicit: line }"></ng-container>
<div *ngFor="let element of line.values">
<ng-container *ngTemplateOutlet="lineContentTmpl,
context: { $implicit: element }"></ng-container>
</div>
</div>
`
})
export class AppTableComponent {
@Input() data: any;
@ContentChild('lineHeader') lineHeaderTmpl: TemplateRef<any>;
@ContentChild('lineContent') lineContentTmpl: TemplateRef<any>;
}
Stackblitz Example
Iterate through ng-content in template
You need wrap the ButtonComponent/InputComponent
content in ng-template
then render the ng-template
in VisualizationControlComponent
. Because the ngTemplateOutlet
cannot render component.
https://stackblitz.com/edit/angular-sjv3p4
ButtonComponent
import { Component, Input, Output, EventEmitter, ViewChild, TemplateRef } from '@angular/core';
@Component({
selector: 'app-button',
template: `
<ng-template>
<button [style.color]="color" (click)="click.emit($event)">
<ng-content></ng-content>
</button>
</ng-template>
`,
})
export class ButtonComponent {
@Input() color: string | null = null;
@Output() click = new EventEmitter<MouseEvent>()
@ViewChild(TemplateRef, { static: true }) templateRef: TemplateRef<void>;
}
VisualizationControlComponent
import { Component, Input, ContentChildren, QueryList } from '@angular/core';
import { ButtonComponent } from './button.component';
@Component({
selector: 'app-visualization-control',
template: `
<table>
<tr>
<td *ngFor="let buttonComponent of buttonComponents">
<ng-container *ngTemplateOutlet="buttonComponent.templateRef"> </ng-container>
</td>
</tr>
</table>
`,
})
export class VisualizationControlComponent {
@ContentChildren(ButtonComponent, { descendants: true }) buttonComponents: QueryList<ButtonComponent>;
}
Use
<app-visualization-control>
<app-button [color]="'#e00'" (click)="onClick($event)">
Button1
</app-button>
<app-button (click)="onClick($event)">
Button2
</app-button>
<app-button>
Button3
</app-button>
</app-visualization-control>
Can an ng-content be used inside of an ngFor?
It is possible but probably doesn't produce the desired result. Children passed by the parent can only projected once (no matter how many <ng-content>
are there. If the <ng-content>
elements don't select specific and different parts of the passed children using the select
attribute, then everything is projected to the first <ng-content>
with the default selector (none).
To make this work you probably want a custom ngFor
that repeats the content passed to <ng-content>
.
NgFor
s ngForTemplate
might help in your use case.
See also Angular2 child component as data
Related Topics
How to Display HTML Code (Entities) on a Web Page
Floated Child Elements: Overflow:Hidden or Clear:Both
Preloading Font with Rel Preload
Right Align and Left Align Text in Same HTML Table Cell
How to Safely Render HTML in React
1Px Calculation Issue with Browsers (Sub-Pixel Problems)
Telephone Numbers and Screen Readers
Why Doesn't Margin:Auto Center an Image
What Is Difference Between <Pre> and <Code> HTML Tag
Break Long Word in Table Cell with Percentage Widths
Flex Items Not Centering Vertically
HTML Form File Uploads Doesn't Upload File
CSS Grids: Align Square Cells with Container Edges with Consistent Grid Gap