Reputation: 7153
I'm trying to conditionally wrap my bootstrap col-xs-4's three to a row. I have my col-xs-4's rendering fine but i can't figure out how to get them condtionally wrapped in a row. Here is my curren't NgFor:
<div class="col-xs-4" *ngFor='let linkGroup of linkGroups | siteMapText: siteMapTextFilter; let i=index'>
<ul class="list-unstyled">
<li class="heading">
<div class="clearfix">
<span [className]="linkGroup.ContainerCssClass">
<span [className]="linkGroup.CssClass"></span>
</span>
<a href="#">
<strong class="heading-text">{{linkGroup.Title}}</strong>
</a>
</div>
</li>
<li *ngFor='let link of linkGroup.Links'>
<a href="#" class="link">
<span [className]="link.CssClass"></span>
<span class="item">{{link.Title}}</span>
</a>
</li>
</ul>
</div>
What I would like to do is something like this, but i know its wrong, so what is the right way to do this:
<div *ngIf='i % 3== 0' class="row">
<div class="col-xs-4" *ngFor='let linkGroup of linkGroups | siteMapText: siteMapTextFilter; let i=index'>
<ul class="list-unstyled">
<li class="heading">
<div class="clearfix">
<span [className]="linkGroup.ContainerCssClass">
<span [className]="linkGroup.CssClass"></span>
</span>
<a href="#">
<strong class="heading-text">{{linkGroup.Title}}</strong>
</a>
</div>
</li>
<li *ngFor='let link of linkGroup.Links'>
<a href="#" class="link">
<span [className]="link.CssClass"></span>
<span class="item">{{link.Title}}</span>
</a>
</li>
</ul>
</div>
</div>
EDIT
The answer was as Gunter suggested, to reorganize the data to be grouped into rows. Then I could organize the template to this:
<div class="row" *ngFor='let linkRow of linkRows| siteMapRowText: siteMapTextFilter'>
<div class="col-xs-4" *ngFor='let rowCol of linkRow.RowGroups'>
<ul class="list-unstyled">
<li class="heading">
<div class="clearfix">
<span [className]="rowCol.ContainerCssClass">
<span [className]="rowCol.CssClass"></span>
</span>
<a href="#">
<strong class="heading-text">{{rowCol.Title}}</strong>
</a>
</div>
</li>
<li *ngFor='let link of rowCol.Links'>
<a href="#" class="link">
<span [className]="link.CssClass"></span>
<span class="item">{{link.Title}}</span>
</a>
</li>
</ul>
</div>
</div>
Upvotes: 3
Views: 3761
Reputation: 657957
Update
You could use a pipe that creates a new array that groups 4 items into an array so you get an array of arrays and then use nested ngFor
@Pipe({ name: 'cols' })
export class ColsPipe implements PipeTransform {
transform(value: any[], cols: number) {
var result: any[] = [];
while(value.length) {
result.push(value.splice(0, cols));
}
return result;
}
};
See also How to split a long array into smaller arrays, with JavaScript
then use it like
@NgModule({
declarations: [ColsPipe],
exports: [ColsPipe]
})
class MySharedModule()
@NgModule({
imports: [MySharedModule],
...
})
class ModuleWhereColsPipeIsUsed {}
@Component({
selector: '...',
template: `
<div class="row" *ngFor='let linkGroupRow of linkGroups | siteMapText: siteMapTextFilter | cols:4; let i=index'>
<div class="col-xs-4" *ngFor='let linkGroup of linkGroupRow'>...</div>
</div>
`
})
original
This can be achieved using ngTemplateOutlet
At first we create a reusable component:
<template #linkGroupTemplate>
<div class="col-xs-4">
<ul class="list-unstyled" let-linkGroup="linkGroup"
>
<li class="heading">
<div class="clearfix">
<span [className]="linkGroup.ContainerCssClass">
<span [className]="linkGroup.CssClass"></span>
</span>
<a href="#">
<strong class="heading-text">{{linkGroup.Title}}</strong>
</a>
</div>
</li>
<li *ngFor='let link of linkGroup.Links'>
<a href="#" class="link">
<span [className]="link.CssClass"></span>
<span class="item">{{link.Title}}</span>
</a>
</li>
</ul>
</div>
</template>
let-linkGroup="linkGroup"
declares a context variable linkGroup
that refers to the linGroup
property of the passed context.
then we use the template inside ngFor
and use it wrapped with <div class="row">
on every 4th item, otherwise unwrapped.
<template ngFor let-linkGroup [ngForOf]="linkGroups |siteMapText:siteMapTextFilter" let-i="index">
<template [ngIf]="i % 3 === 0">
<div class="row">
<template [ngTemplateOutlet]="linkGroupTemplate" [ngOutletContext]="{'linkGroup': linkGroup}">
</div>
<template>
<template [ngIf]="i % 3 !== 0">
<template [ngTemplateOutlet]="linkGroupTemplate" [ngOutletContext]="{'linkGroup': linkGroup}">
</template>
</template>
<ng-container *ngFor="let linkGroup of linkGroups |siteMapText:siteMapTextFilter" let-i="index">
<ng-container *ngIf="i % 3 === 0">
<div class="row">
<template [ngTemplateOutlet]="linkGroupTemplate" [ngOutletContext]="{'linkGroup': linkGroup}"></template>
</div>
<ng-container>
<ng-container *ngIf="i % 3 !== 0">
<template [ngTemplateOutlet]="linkGroupTemplate" [ngOutletContext]="{'linkGroup': linkGroup}"></template>
</ng-container>
</ng-container>
With [ngOutletContext]="{'linkGroup': linkGroup}"
we wrap the actual linkGroup
value in another object so we can access it as before within the template. Otherwise we would need to declare a variable for each property we want to use
let-CssContainerCssClass="ContainerCssClass" let-CssClass="CssClass" let-Title="Title" let-Links="Links"
and remove linkGroup.
from all bindings.
See also How to repeat a piece of HTML multiple times without ngFor and without another @Component for an example (with Plunker) that uses ngTemplateOutlet
and ngOutletContext
.
Upvotes: 4