Learn how to use Virtual Scrolling in Angular
By: Sam Orgill
Tags: Angular, Programming, Web Development, Tutorial, Software Development

Step 1

In the terminal (or command line) installed the CDK along with Material & Animations. Enter the following command:

npm i --save @angular/cdk @angular/material @angular/animations

Then add a prebuilt Angular theme to the styles.css file:

@import ‘@angular/material/prebuilt-themes/deeppurple-amber.css’;

Step 2

In your app.module.ts file, import the ScrollingModule and the MatCardModule (we’ll be using cards to present our data).

// app.module.ts import {BrowserModule} from '@angular/platform-browser';

import {NgModule} from '@angular/core';

import {ScrollingModule} from '@angular/cdk/scrolling';



import {AppComponent} from './app.component';

import {MatCardModule} from '@angular/material';



@NgModule({

declarations: [

AppComponent

],

imports: [

BrowserModule,

ScrollingModule,

MatCardModule

],

providers: [],

bootstrap: [AppComponent]

})

export class AppModule {

}

Step 3

Create some mock data in a new file called mock-data , I've created some dummy data from NFL teams, but feel free to use whatever you like:

// mock-data.ts export const teams = [

{

name: 'Arizona Cardinals',

league: 'NFC West',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/ARI.svg'

},

{

name: ' Atlanta Falcons',

league: 'NFC South',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/ATL.svg'

},

{

name: 'Baltimore Ravens',

league: 'AFC North',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/BAL.svg'

},

{

name: 'Buffalo Bills',

league: 'AFC East',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/BUF.svg'

},

{

name: 'Carolina Panthers',

league: 'NFC South',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/CAR.svg'

},

{

name: 'Chicago Bears',

league: 'NFC North',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/CHI.svg'

},

{

name: 'Cincinnati Bengals',

league: 'AFC North',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/CIN.svg'

},

{

name: 'Cleveland Browns',

league: 'AFC North',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/CLE.svg'

},

{

name: 'Dallas Cowboys',

league: 'NFC East',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/DAL.svg'

},

{

name: 'Denver Broncos',

league: 'AFC West',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/DEN.svg'

},

{

name: 'Detroit Lions',

league: 'NFC North',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/DET.svg'

},

{

name: 'Green Bay Packers',

league: 'NFC North',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/GB.svg'

},

{

name: 'Houston Texans',

league: 'AFC South',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/HOU.svg'

},

{

name: 'Indianapolis Colts',

league: 'AFC South',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/IND.svg'

},

{

name: 'Jacksonville Jaguars',

league: 'AFC South',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/JAX.svg'

},

{

name: 'Kansas City Chiefs',

league: 'AFC West',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/KC.svg'

},

{

name: 'Los Angeles Chargers',

league: 'AFC West',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/LAC.svg'

},

{

name: 'Los Angeles Rams',

league: 'NFC West',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/LA.svg'

},

{

name: 'Miami Dolphins',

league: 'AFC East',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/MIA.svg'

},

{

name: 'Minnesota Vikings',

league: 'NFC North',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/MIN.svg'

},

{

name: 'New England Patriots',

league: 'AFC East',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/NE.svg'

},

{

name: 'New Orleans Saints',

league: 'NFC East',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/NO.svg'

},

{

name: 'New York Giants',

league: 'NFC South',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/NYG.svg'

},

{

name: 'New York Jets',

league: 'AFC East',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/NYJ.svg'

},

{

name: 'Oakland Raiders',

league: 'AFC West',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/OAK.svg'

},

{

name: 'Philadelphia Eagles',

league: 'NFC East',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/PHI.svg'

},

{

name: 'PittsBurgh Steelers',

league: 'AFC North',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/PIT.svg'

},

{

name: 'San Francisco 49ers',

league: 'NFC West',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/SF.svg'

},

{

name: 'Seattle Seahawks',

league: 'NFC West',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/SEA.svg'

},

{

name: 'Tampa Bay Buccaneers',

league: 'NFC South',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/TB.svg'

},

{

name: 'Tennessee Titans',

league: 'AFC South',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/TEN.svg'

},

{

name: 'Washington Redskins',

league: 'NFC East',

image: 'https://static.nfl.com/static/site/img/logos/svg/teams/WAS.svg'

}

];

Step 4

Create a teams.service by using the following command in your terminal (or command line):

ng g s --spec=false teams

This will create a service for you from where we can expose our mock data our app, so in your teams.service.ts file create a getTeams() method to expose your teams. teams.service.ts should now look like:

// teams.service.ts import {Injectable} from '@angular/core';

import {teams} from './mock-data';



@Injectable({

providedIn: 'root'

})

export class TeamsService {



constructor() {

}



getTeams() {

return teams;

}

}

Step 5

Generate a new component called teams using the following command in the terminal (or command line):

ng g c --spec=false teams

Once your component has been created, go into your app.component.html , remove all the HTML & stock text and add in the selector for our new component <app-teams></app-teams> . This is so we can now see our component in the browser.

Fire up your app with the command ng serve and verify you can now see your new teams.component .

Step 6

In your teams.component.ts file we now need to create a variable called teams and set it to an empty array:

teams = [];

Then we need to import our Teams Service at the top of the file:

import {TeamsService} from '../teams.service';

Next, we must inject our service into our component, we do this in the constructor:

constructor(private teamsService: TeamsService) {}

Now, we can access the getTeams() method we created in the service to get hold of our data. We need to fill our components teams array with the data exposed from the service. So we will create a new method called getTeams(){} and within this method we need to set the local array equal to the getTeams() method we created in the service:

getTeams(){

this.teams = this.teamsService.getTeams();

}

We now need to ensure we call this getTeams() method when our component is initialised and we go this by calling it in the ngOnInit() method:

ngOnInit() {

this.getTeams();

}

We have now created our component, injected our service and grabbed the data we need to consume. You teams.component.ts should now look like this:

// teams.component.ts import {Component, OnInit} from '@angular/core';

import {TeamsService} from '../teams.service';



@Component({

selector: 'app-teams',

templateUrl: './teams.component.html',

styleUrls: ['./teams.component.css']

})

export class TeamsComponent implements OnInit {



teams = [];



constructor(private teamsService: TeamsService) {

}



ngOnInit() {

this.getTeams();

}



getTeams(){

this.teams = this.teamsService.getTeams();

}



}

Step 7

Now we need to display this data on the front end, in your teams.component.ts file create a container

<div class="container"> </div>

Within this container we need to create a cdk-virtual-scroll-viewport and what this will be is a container which containers our data and allows us to scroll through it.

One of the properties for the viewport is itemSize and this should be equal to the heigh of each item you’re displaying. In our example we are display Material Cards, we could set them to any height but I’m going to set ours to 200px in the CSS later, so set the itemSize to “200” and give the viewport a class name of “viewport”. Your HTML should now look like this:

<div class="container">

<cdk-virtual-scroll-viewport itemSize="200" class="viewport">

<!--TODO: Add material card-->

</cdk-virtual-scroll-viewport>

</div>

Next, we need to add our material card. Within the mat-card tag we then need to iterate over our array of teams. Normally in Angular we would use the *ngFor directive, however the ScrollingModule provides a virtual version of *ngFor called *cdkVirtualFor and it is this magical directive that will help load and unload the cards from the DOM. Within *cdkVirtualFor we can loop over items just as we would with *ngFor :

<mat-card *cdkVirtualFor="let team of teams"> </mat-card>

Within this card tag you can add material card elements such as mat-card-title , img mat-card-image and mat-card-content adding the information where you like. Your teams.component.html file should now look like:

<!--teams.component.html-->

<div class="container">

<cdk-virtual-scroll-viewport itemSize="200" class="viewport">

<mat-card *cdkVirtualFor="let team of teams">

<mat-card-title>{{team.name}}</mat-card-title>

<img mat-card-image [src]="team.image" [alt]="team.name">

<mat-card-content>League {{team.league}}</mat-card-content>

</mat-card>

</cdk-virtual-scroll-viewport>

</div>

Step 8

The last thing we need to do is add some styling to our component to open up our view port and generally make things a little pretty. You can use my CSS or style it however you like

/*teams.component.css*/ .container {

display: flex;

align-self: center;

}



mat-card {

height: 200px;

margin: 2em;

background-color: #1B264F;

color: white;

}



img {

width: 20%;

padding-left: 1em;

}



.viewport {

height: 400px;

width: 100%;

background: #C4BBB8;

margin: 2em 8em 8em;

box-shadow: 1px 1px 1px grey;

}

Step 9

Navigate to http://localhost:4200 in your browser, open your dev tools:

Mac: Option+Command + i | Windows: F12

You will notice that only 3 cards are loaded in the DOM, despite there being 32 teams in our list. As you scroll down your list you’ll see new cards being added and old cards removed from the DOM as they come in and out of view.

Thats the magic of Virtual Scrolling in Angular 7!