Angular Virtual ForOf Directive Overview
The Ignite UI for Angular igxForOf directive is an alternative to ngForOf for templating large amounts of data. It uses virtualization behind the scenes to optimize DOM rendering and memory consumption.
Angular Virtual For Directive Example
Like this sample? Get access to our complete Ignite UI for Angular toolkit and start building your own apps in minutes. Download it for free.
Getting Started with Ignite UI for Angular Virtual ForOf Directive
To get started with the Ignite UI for Angular igxFor
directive, first you need to install Ignite UI for Angular. In an existing Angular application, type the following command:
ng add igniteui-angular
cmd
For a complete introduction to the Ignite UI for Angular, read the getting started topic.
The next step is to import the IgxForOfModule
in your app.module.ts file.
// app.module.ts
import { IgxForOfModule } from 'igniteui-angular';
// import { IgxForOfModule } from '@infragistics/igniteui-angular'; for licensed package
@NgModule({
imports: [
...
IgxForOfModule,
...
]
})
export class AppModule {}
typescript
Alternatively, as of 16.0.0
you can import the IgxForOfDirective
as a standalone dependency.
// home.component.ts
import { IgxForOfDirective } from 'igniteui-angular';
// import { IgxForOfDirective } from '@infragistics/igniteui-angular'; for licensed package
@Component({
selector: 'app-home',
template: `
<span #container>
<ng-template *igxFor="data"></ng-template>
</span>
`,
styleUrls: ['home.component.scss'],
standalone: true,
imports: [IgxForOfDirective]
})
export class HomeComponent {
public data: Employee [];
}
typescript
Now that you have the Ignite UI for Angular Tree Grid module or directives imported, you can start using the igxFor
directive.
Using the Angular Virtual ForOf
Now that we have the module or directive imported, let’s get started with a basic configuration of the igxFor
that binds to local data:
<span #container>
<ng-template *igxFor="data"></ng-template>
</span>
html
The data property is an array that provides the data objects used to construct the virtualized DOM.
Examples
The igxFor
directive can be used to virtualize the data in vertical, horizontal or both directions.
Virtualization works similarly to Paging by slicing the data into smaller chucks which are swapped from a container viewport while the user scrolls the data horizontally/vertically. The difference with the Paging is that virtualization mimics the natural behavior of the scrollbar. The igxFor
directive is creating scrollable containers and renders small chunks of the data. It is used inside the igxGrid
and it can be used to build a virtual igx-list
.
Vertical virtualization
<igx-list>
<div [style.height]="'500px'" [style.overflow]="'hidden'" [style.position]="'relative'">
<igx-list-item [style.width]="'calc(100% - 18px)'"
*igxFor="let item of data | igxFilter: fo;
scrollOrientation : 'vertical';
containerSize: '500px';
itemSize: '50px'">
<div class="contact">
<span class="name">{{item.name}}</span>
</div>
</igx-list-item>
</div>
</igx-list>
html
Note: It is strongly advised that the parent container of the igxForOf
template has the following CSS rules applied: height
for vertical and width
for horizontal, overflow: hidden
and position: relative
. This is because the smooth scrolling behavior is achieved through content offsets that could visually affect other parts of the page if they remain visible.
Horizontal virtualization
<igx-list>
<div [style.width]="'880px'" [style.overflow]="'hidden'" [style.position]="'relative'">
<igx-list-item [style.width]="'220px'"
*igxFor="let item of data | igxFilter: fo;
scrollOrientation : 'horizontal';
containerSize: '880px';
itemSize: '220px'">
<div class="contact">
<span class="name">{{item.name}}</span>
</div>
</igx-list-item>
</div>
</igx-list>
html
Horizontal and vertical virtualization
<table #container [style.width]='width'
[style.height]='height'
[style.overflow]='"hidden"'
[style.position]='"relative"'>
<ng-template #scrollContainer igxFor let-rowData
[igxForOf]="data"
[igxForScrollOrientation]="'vertical'"
[igxForContainerSize]='height'
[igxForItemSize]='"50px"'>
<tr [style.display]="'flex'" [style.height]="'50px'">
<ng-template #childContainer igxFor let-col
[igxForOf]="cols"
[igxForScrollOrientation]="'horizontal'"
[igxForScrollContainer]="parentVirtDir"
[igxForContainerSize]='width'>
<td [style.min-width]='col.width + "px"'>
{{rowData[col.field]}}
</td>
</ng-template>
</tr>
</ng-template>
</table>
html
The igxFor
directivе is used to virtualize data in both vertical and horizontal directions inside the igxGrid
.
Follow the Grid Virtualization topic for more detailed information and demos.
igxFor bound to remote service
The igxForOf
directive can be bound to a remote service using the Observable
property - remoteData
(in the following case). The chunkLoading
event should also be utilized to trigger the requests for data.
<div style='height: 500px; overflow: hidden; position: relative;'>
<ng-template igxFor let-item [igxForOf]="remoteData | async"
(chunkPreload)="chunkLoading($event)"
[igxForScrollOrientation]="'vertical'"
[igxForContainerSize]='"500px"'
[igxForItemSize]='"50px"'
[igxForRemote]='true'
let-rowIndex="index" #virtDirRemote>
<div style='height:50px;'>{{item.ProductID}} : {{item.ProductName}}</div>
</ng-template>
</div>
html
Note: There is a requirement to set the totalItemCount
property in the instance of igxForOf
.
this.virtDirRemote.totalItemCount = data['@odata.count'];
typescript
In order to access the directive instance from the component, it should be marked as ViewChild
:
@ViewChild('virtDirRemote', { read: IgxForOfDirective })
public virtDirRemote: IgxForOfDirective<any>;
typescript
After the request for loading the first chunk, the totalItemCount
can be set:
public ngAfterViewInit() {
this.remoteService.getData(this.virtDirRemote.state, (data) => {
this.virtDirRemote.totalItemCount = data['@odata.count'];
});
}
typescript
When requesting data you can take advantage of the IgxForOfState
interface, which provides the startIndex
and chunkSize
properties. Note that initially the chunkSize will be 0, so you have to specify the size of the first loaded chunk (the best value is the initial igxForContainerSize
divided by the igxForItemSize
).
public getData(data?: IForOfState, cb?: (any) => void): any {
var dataState = data;
return this.http
.get(this.buildUrl(dataState))
.map((response) => response.json())
.map((response) => {
return response;
})
.subscribe((data) => {
this._remoteData.next(data.value);
if (cb) {
cb(data);
}
});
}
private buildUrl(dataState: any): string {
let qS: string = '?', requiredChunkSize: number;
if (dataState) {
const skip = dataState.startIndex;
requiredChunkSize = dataState.chunkSize === 0 ?
// Set initial chunk size, the best value is igxForContainerSize
// initially divided by igxForItemSize
10 : dataState.chunkSize;
const top = requiredChunkSize;
qS += `$skip=${skip}&$top=${top}&$count=true`;
}
return `${this.url}${qS}`;
}
typescript
Every time the chunkPreload
event is thrown, a new chunk of data should be requested:
chunkLoading(evt) {
if(this.prevRequest){
this.prevRequest.unsubscribe();
}
this.prevRequest = this.remoteService.getData(evt, ()=> {
this.virtDirRemote.cdr.detectChanges();
});
}
typescript
Local Variables
The igxFor
directive includes the following helper properties in its context: even
, odd
, first
and last
. They are used to identify the current element position in the collection. The following code snippet demonstrates how to use the even
property in an ng-template
. Аn even
class will be assigned to every even div element:
<ng-template igxFor let-item let-isEven="even"
[igxForOf]="data"
[igxForScrollOrientation]="'vertical'" >
<div [ngClass]="{even: isEven}"></div>
</ng-template>
html
Known Limitations
Limitation | Description |
---|---|
scrollTo method does not work correctly when the content size of the rendered templates changes post initialization |
When the elements inside the template have a size, that changes runtime after initialization (for example as a result of content projection, remote request resolution etc.), then the scrollTo method will not be able to scroll to the correct index. The method will scroll to the position of the index before the runtime size change occurs, hence the location will not be correct after the size is changed later. A possible workaround is to use templates that do not change their size based on their content if the content is loaded later. |