Angular 7 Virtual Scroll example – Angular Material CDK

angular-7-virtual-scroll-example-meterial-cdk-feature-image

The @angular/cdk/scrolling module with a technique called Virtual Scrolling helps us display a big list of elements efficiently by only rendering the items in view. Virtual Scrolling is different from infinite scroll – where it renders batches of elements and then when user goes to bottom of the list, it renders the rest. In this tutorial, we create many simple examples that show you how to work with Angular 7 Material CDK – Virtual Scrolling.

Related Post: Angular 7 Drag and Drop example – Angular Material CDK

Setup Angular 7 Material CDK

– Run cmd:
npm install @angular/cdk

– Import ScrollingModule into NgModule:

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

@NgModule({
  declarations: [...],
  imports: [
    ...
    ScrollingModule
  ],
  ...
})
export class AppModule { }

Angular 7 Virtual Scroll

Generate a list of 1000 random number items:

items = Array(1000).fill(0).map(() => Math.round(Math.random() * 100));

Create Viewport

We use <cdk-virtual-scroll-viewport> directive with *cdkVirtualFor inside (loop same as *ngFor).
To indicate the size of each item (it maybe height or width depending on the orientation of the Viewport), we use itemSize input property.

<cdk-virtual-scroll-viewport class="gkz-viewport" [itemSize]="20">
  <div *cdkVirtualFor="let item of items; let i = index" class="item">
    Item #{{i}}: {{item}}
  </div>
</cdk-virtual-scroll-viewport>

Style with .cdk-virtual-scroll-content-wrapper (wrapper element that contains the rendered content):

.gkz-viewport {
    height: 200px;
    width: 200px;
    border: 1px solid black;
    .cdk-virtual-scroll-content-wrapper {
        display: flex;
        flex-direction: column;
    }
    .item {
        height: 19px;
        border-bottom: 1px dashed #aaa;
    }
}

angular-virtual-scroll-example-create-viewport

Use Context Variables

With *cdkVirtualFor, we have some context variables: index, count, first, last, even, odd.

<cdk-virtual-scroll-viewport class="gkz-viewport" [itemSize]="20*6">
  <div [class.item-alternate]="odd" *cdkVirtualFor="
    let item of items;
    let i = index;
    let count = count;
    let first = first;
    let last = last;
    let even = even;
    let odd = odd;">
    <div class="item">Item #{{i}}: {{item}}</div>
    <div class="item">count: {{count}}</div>
    <div class="item">first: {{first}}</div>
    <div class="item">last: {{last}}</div>
    <div class="item">even: {{even ? 'Yes' : 'No'}}</div>
    <div class="item">odd: {{odd ? 'Yes' : 'No'}}</div>
  </div>
</cdk-virtual-scroll-viewport>

With item-alternate style:


.gkz-viewport {
    ...
    .item-alternate {
        background: #ddd;
    }
}

angular-virtual-scroll-example-custom-variables

Reduce memory on Caching

By default, *cdkVirtualFor caches created items to improve rendering performance.
We can adjusted size of the view cache using templateCacheSize property (0 means disabling caching).

<cdk-virtual-scroll-viewport class="gkz-viewport" [itemSize]="20">
  <div *cdkVirtualFor="let item of items; let i = index; templateCacheSize: 0" class="item">
    Item #{{i}}: {{item}}
  </div>
</cdk-virtual-scroll-viewport>

Horizontal Viewport

By default, the orientation of Viewport is vertical, we can change it by setting orientation="horizontal".
Notice that, instead of height, itemSize property in horizontal orienttation specifies width.

<cdk-virtual-scroll-viewport orientation="horizontal" itemSize="50" class="gkz-horizontal-viewport">
  <div *cdkVirtualFor="let item of items; let i = index" class="item">
    Item #{{i}}: {{item}}
  </div>
</cdk-virtual-scroll-viewport>

We also need to target CSS at .cdk-virtual-scroll-content-wrapper (the wrapper element contains the rendered content).

.gkz-horizontal-viewport {
    height: 120px;
    width: 200px;
    border: 1px solid black;
    .cdk-virtual-scroll-content-wrapper {
        display: flex;
        flex-direction: row;
    }
    .item {
        width: 48px;
        height: 100px;
        border: 1px dashed #aaa;
        display: inline-flex;
        justify-content: center;
        align-items: center;
        writing-mode: vertical-lr;
    }
}

angular-virtual-scroll-example-horizontal-viewport

Custom Buffer Parameters

When using itemSize directive for fixed size strategy, we can set 2 more buffer parameters: minBufferPx & maxBufferPx that calculate the extra content to be rendered beyond visible items in the Viewport:
– if the Viewport detects that there is less buffered content than minBufferPx it will immediately render more.
– the Viewport will render at least enough buffer to get back to maxBufferPx.

For example, we set: itemSize = 20, minBufferPx = 50, maxBufferPx = 200.
The viewport detects that buffer remaining is only 40px (2 items).
40px < minBufferPx: render more buffer.
– render an additional 160px (8 items) to bring the total buffer size to 40px + 160px = 200px >= maxBufferPx.

angular-virtual-scroll-example-custom-buffer-params

Angular 7 Virtual Scroll with Specific Data

As well as Array, *cdkVirtualFor can work with DataSource or Observable<Array>.

DataSource

A DataSource is an abstract class with two methods:
connect(): is called by the Viewport to receive a stream that emits the data array.
disconnect(): will be invoked when the Viewport is destroyed.

In this example, we will create a DataSource with 1000 items, and simulate fetching data from server using setTimeout() function.

import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { DataSource, CollectionViewer } from '@angular/cdk/collections';
...
export class SpecDataComponent {
  dataSource = new MyDataSource();
}

export class MyDataSource extends DataSource<string | undefined> {
  private PAGE_SIZE = 10;
  private fetchedPages = new Set<number>();

  private cachedData = Array.from<string>({ length: 1000 });
  private dataStream = new BehaviorSubject<(string | undefined)[]>(this.cachedData);

  private subscription = new Subscription();

  connect(collectionViewer: CollectionViewer): Observable<(string | undefined)[]> {
    this.subscription.add(collectionViewer.viewChange.subscribe(range => {
      const startPage = this.getPageForIndex(range.start);
      const endPage = this.getPageForIndex(range.end);
      for (let i = startPage; i <= endPage; i++) {
        this.fetchPage(i);
      }
    }));

    return this.dataStream;
  }

  disconnect(): void {
    this.subscription.unsubscribe();
  }

  private getPageForIndex(index: number): number {
    return Math.floor(index / this.PAGE_SIZE);
  }

  private fetchPage(page: number) {
    if (this.fetchedPages.has(page)) {
      return;
    }
    this.fetchedPages.add(page);

    // simulate fetching data from server
    setTimeout(() => {
      this.cachedData.splice(page * this.PAGE_SIZE, this.PAGE_SIZE,
        ...Array.from({ length: this.PAGE_SIZE })
          .map((_, i) => `Item #${page * this.PAGE_SIZE + i}`));

      this.dataStream.next(this.cachedData);
    }, 2000);
  }
}

Iterating DataSource by *cdkVirtualFor is just like working with an Array:

<cdk-virtual-scroll-viewport itemSize="20" class="gkz-viewport">
  <div *cdkVirtualFor="let item of dataSource" class="item">{{item || 'Loading...'}}</div>
</cdk-virtual-scroll-viewport>

angular-virtual-scroll-example-datasource

Observable

We create an BehaviorSubject that emit an Array everytime user clicks on Button:

observableData = new BehaviorSubject<number[]>([]);

emitData() {
  const items = Array(3).fill(0).map(() => Math.round(Math.random() * 100));
  const data = this.observableData.value.concat(items);
  this.observableData.next(data);
}

Iterating Observable Data by *cdkVirtualFor is just like working with an Array:

<button (click)="emitData()">Add item</button>
<cdk-virtual-scroll-viewport class="gkz-viewport" [itemSize]="20">
  <div *cdkVirtualFor="let item of observableData | async; let i = index" class="item">
    Item #{{i}}: {{item}}
  </div>
</cdk-virtual-scroll-viewport>

angular-virtual-scroll-example-observable-data

Angular 7 Virtual Scroll trackBy

We create an Observable data that will be changed (sorted):

import { BehaviorSubject } from 'rxjs';

interface Customer {
  id: number;
  name: string;
}

export class TrackbyComponent {
  customers = [
    { id: 1, name: 'John Bailey' },
    { id: 2, name: 'Amelia Kerr' },
    { id: 3, name: 'Julian Wallace' },
    { id: 4, name: 'Pippa Sutherland' },
    { id: 5, name: 'Stephanie Simpson' },
    ...
  ];

  customersObservable = new BehaviorSubject(this.customers);

  sortBy(prop: 'id' | 'name') {
    this.customersObservable.next(this.customers.map(s => ({ ...s })).sort((a, b) => {
      const aProp = a[prop], bProp = b[prop];
      if (aProp < bProp) {
        return -1;
      } else if (aProp > bProp) {
        return 1;
      }
      return 0;
    }));
  }
}

To check trackBy, we will test 3 cases:
– no trackBy function
trackBy index
trackBy name field

No trackBy function


<button (click)="sortBy('id')">Sort by id</button>
<button (click)="sortBy('name')">Sort by name</button>
<cdk-virtual-scroll-viewport class="gkz-viewport" [itemSize]="20">
  <div *cdkVirtualFor="let customer of customersObservable | async" class="customer-item">
    {{customer.id}} - {{customer.name}}
  </div>
</cdk-virtual-scroll-viewport>

angular-virtual-scroll-example-no-trackby

With trackBy function

trackBy function works the same as the *ngFor trackBy.

indexTrackFn = (index: number) => index;
nameTrackFn = (_: number, item: Customer) => item.name;

trackBy index

<button (click)="sortBy('id')">Sort by id</button>
<button (click)="sortBy('name')">Sort by name</button>
<cdk-virtual-scroll-viewport class="gkz-viewport" [itemSize]="20">
  <div *cdkVirtualFor="let customer of customersObservable | async; trackBy: indexTrackFn" class="customer-item">
    {{customer.id}} - {{customer.name}}
  </div>
</cdk-virtual-scroll-viewport>

angular-virtual-scroll-example-trackby-index

trackBy a field

<button (click)="sortBy('id')">Sort by id</button>
<button (click)="sortBy('name')">Sort by name</button>
<cdk-virtual-scroll-viewport class="gkz-viewport" [itemSize]="20">
  <div *cdkVirtualFor="let customer of customersObservable | async; trackBy: nameTrackFn" class="customer-item">
    {{customer.id}} - {{customer.name}}
  </div>
</cdk-virtual-scroll-viewport>

angular-virtual-scroll-example-trackby-field

Angular 7 Virtual Scroll Strategy

Instead of using the itemSize directive on the Viewport, we can provide a custom strategy by creating a class that implements the VirtualScrollStrategy interface and providing it as the VIRTUAL_SCROLL_STRATEGY on the @Component.

import { Component, ChangeDetectionStrategy } from '@angular/core';
import { FixedSizeVirtualScrollStrategy, VIRTUAL_SCROLL_STRATEGY } from '@angular/cdk/scrolling';

export class CustomVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
  constructor() {
    super(20, 50, 200); // (itemSize, minBufferPx, maxBufferPx)
  }
}

@Component({
  selector: 'app-scrolling-strategy',
  templateUrl: './scrolling-strategy.component.html',
  styleUrls: ['./scrolling-strategy.component.scss'],
  providers: [{ provide: VIRTUAL_SCROLL_STRATEGY, useClass: CustomVirtualScrollStrategy }]
})
export class ScrollingStrategyComponent {
  items = Array(1000).fill(0).map(() => Math.round(Math.random() * 100));
}

In the example, our custom strategy class extends FixedSizeVirtualScrollStrategy that implements VirtualScrollStrategy.

<cdk-virtual-scroll-viewport class="gkz-viewport">
  <div *cdkVirtualFor="let item of items; let i = index" class="item">
    Item #{{i}}: {{item}}
  </div>
</cdk-virtual-scroll-viewport>

angular-virtual-scroll-example-scroll-strategy

Source Code

AngularMaterialScrolling

101 thoughts on “Angular 7 Virtual Scroll example – Angular Material CDK”

  1. I like the helpful info you provide in your articles.
    I’ll bookmark your blog and check again here frequently.

    I am quite certain I’ll learn many new stuff right here!
    Good luck for the next!

  2. Hi, I think your website might be having browser compatibility issues. When I look at your website in Chrome, it looks fine but when opening in Internet Explorer, it has some overlapping. I just wanted to give you a quick heads up! Other then that, superb blog!

  3. hey there and thank you for your info – I’ve certainly picked up anything new from right here.
    I did however expertise several technical points using this site,
    since I experienced to reload the site many times previous to I could
    get it to load correctly. I had been wondering if your hosting is OK?
    Not that I am complaining, but sluggish loading instances
    times will very frequently affect your placement in google
    and can damage your high-quality score if advertising and
    marketing with Adwords. Anyway I am adding this RSS to my email and could look
    out for much more of your respective interesting content. Ensure that you update this
    again soon.

  4. If some оne needs to Ьbe uрdated with latest technologies then he must be payy a qսick visit this websitе and be upp to date all
    the time.

  5. Generally I do not learn article on blogs, but
    I wish to say that this write-up very forced me to take a look at and do it!

    Your writing style has been amazed me. Thanks, quite great
    post.

  6. What i do not understood is actually how you are now not really much more well-liked than you
    may be now. You are very intelligent. You know therefore considerably on the subject of
    this subject, made me individually believe it from a lot of numerous angles.
    Its like men and women don’t seem to be fascinated unless it’s something to accomplish with Woman gaga!

    Your individual stuffs excellent. Always care for it up!

  7. It’s a pity you don’t have a donate button! I’d most
    certainly donate to this superb blog! I guess for now i’ll settle for bookmarking and adding your RSS feed
    to my Google account. I look forward to brand new updates and will share this website with my Facebook group.
    Talk soon!

  8. My coder is trying to convince me to move to .net from
    PHP. I have always disliked the idea because of the expenses.
    But he’s tryiong none the less. I’ve been using WordPress on several websites for about a year and am nervous about switching to another platform.
    I have heard great things about blogengine.net. Is there a way I can import all
    my wordpress content into it? Any kind of help would be really appreciated!

  9. Today, I went to the beachfront with my children. I found a sea shell and gave it to my 4 year old daughter
    and said “You can hear the ocean if you put this to your ear.”
    She put the shell to her ear and screamed.
    There was a hermit crab inside and it pinched her ear.
    She never wants to go back! LoL I know this is entirely off topic but I had to
    tell someone!

  10. Great items from you, man. I’ve be aware your stuff prior to and
    you’re simply too fantastic. I actually like what you have got here, really like what you are stating and the way in which you assert it.
    You are making it entertaining and you continue to take care
    of to stay it wise. I can’t wait to read much more from you.
    This is really a terrific web site.

  11. Awesome blog! Do you have any helpful hints for aspiring
    writers? I’m planning to start my own blog soon but I’m
    a little lost on everything. Would you advise starting with a free platform like WordPress or go
    for a paid option? There are so many choices out there that I’m totally overwhelmed ..
    Any recommendations? Kudos!

  12. you are really a good webmaster. The site loading speed is incredible. It seems that you are doing any unique trick. In addition, The contents are masterwork. you have done a wonderful job on this topic!

  13. Fantastic blog! Do you have any suggestions for aspiring writers? I’m planning to start my own site soon but I’m a little lost on everything. Would you recommend starting with a free platform like WordPress or go for a paid option? There are so many options out there that I’m totally overwhelmed .. Any suggestions? Thanks!

  14. I absolutely love your blog and find a lot of your post’s to be exactly I’m looking for. Would you offer guest writers to write content for yourself? I wouldn’t mind composing a post or elaborating on a lot of the subjects you write about here. Again, awesome blog!

  15. I was curious if you ever considered changing the structure of your blog? Its very well written; I love what youve got to say. But maybe you could a little more in the way of content so people could connect with it better. Youve got an awful lot of text for only having one or two images. Maybe you could space it out better?

  16. I think this is one of the most significant info for me.
    And i’m glad reading your article. But wanna remark on some general things, The website style is ideal, the articles is really nice : D.
    Good job, cheers

  17. At this time it appears like WordPress is
    the best blogging platform available right now. (from what I’ve read) Is
    that what you are using on your blog?

  18. When someone writes an piece of writing he/she keeps the image of a user in his/her mind that how a user
    can be aware of it. Therefore that’s why this article is perfect.
    Thanks!

  19. I was suggested this web site by my cousin. I am not sure whether this post is written by him as nobody else know such detailed about
    my problem. You’re amazing! Thanks!

  20. Nice post. I was checking constantly this blog and I’m impressed!
    Extremely useful info specifically the last part :
    ) I care for such information much. I was looking for this certain information for a long time.
    Thank you and best of luck.

  21. Hi there, just became alert to your blog through
    Google, and found that it is truly informative. I am
    going to watch out for brussels. I’ll be grateful if you continue this in future.
    Lots of people will be benefited from your writing.
    Cheers!

  22. Thanks , I have recently been searching for information about this subject for ages and yours is the best I have found out till
    now. But, what concerning the conclusion? Are you certain concerning the source?

  23. Link exchange is nothing else however it is simply placing
    the other person’s website link on your page at appropriate place and other person will also do same for you.

  24. I blog often and I seriously thank you for your information. This article has really peaked my interest.

    I will take a note of your blog and keep checking for new information about once a week.

    I subscribed to your Feed as well.

  25. I believe everything posted made a great deal of sense.
    However, what about this? what if you were to write a awesome post title?
    I mean, I don’t want to tell you how to run your website, however suppose you added something that grabbed a person’s attention? I mean ozenero
    | Mobile & Web Programming Tutorials is kinda vanilla. You might look at Yahoo’s home page and see how they create post headlines to grab people interested.
    You might try adding a video or a pic or two to get readers
    excited about what you’ve written. Just my opinion, it would make your posts a little bit more interesting.

  26. Heya i am for the first time here. I came across this board and I find It truly useful
    & it helped me out much. I hope to give something back and help others like you aided
    me.

  27. This design is spectacular! You definitely know how to keep
    a reader entertained. Between your wit and your videos,
    I was almost moved to start my own blog (well, almost…HaHa!)
    Fantastic job. I really enjoyed what you had to say, and more
    than that, how you presented it. Too cool!

  28. Just wish to say your article is as amazing. The clearness in your post is just great and i could assume you are an expert on this subject.

    Fine with your permission let me to grab your feed to keep updated with
    forthcoming post. Thanks a million and please continue the
    enjoyable work.

  29. Wow, incredible blog layout! How long have you been blogging for?
    you make blogging look easy. The overall look of your web site
    is wonderful, as well as the content!

  30. I get pleasure from, cause I found exactly what I used to be having a look for.
    You’ve ended my four day long hunt! God Bless you
    man. Have a nice day. Bye

  31. Greetings I am so thrilled I found your website,
    I really found you by mistake, while I was researching on Askjeeve for something else, Nonetheless I am here now and would just like
    to say many thanks for a marvelous post and a all round interesting blog (I also love the theme/design),
    I don’t have time to browse it all at the moment but
    I have bookmarked it and also added in your RSS feeds, so when I have time
    I will be back to read a great deal more, Please
    do keep up the excellent job.

  32. Have you ever considered about including a little bit more than just
    your articles? I mean, what you say is valuable and all. Nevertheless think about
    if you added some great photos or video clips to give your posts more, “pop”!
    Your content is excellent but with images and videos, this website could certainly be one of the most beneficial in its field.

    Fantastic blog!

  33. Write more, thats all I have to say. Literally, it seems as though you relied on the
    video to make your point. You definitely know what
    youre talking about, why throw away your intelligence on just posting videos
    to your weblog when you could be giving us something informative to read?

  34. Hello there! I could have sworn I’ve been to this web site before
    but after going through some of the posts I realized it’s new to me.
    Anyways, I’m definitely happy I discovered it and I’ll be book-marking it and checking back regularly!

  35. Hello there! I could have sworn I’ve been to this blog before but after browsing through some
    of the post I realized it’s new to me. Anyways, I’m definitely happy I found it and I’ll be bookmarking and checking back often!

  36. I am really impressed with your writing abilities as neatly as with the layout in your weblog.

    Is this a paid subject or did you customize it yourself?

    Either way stay up the excellent quality writing, it’s rare to see a great weblog like this one these days..

  37. Hi, i think that i noticed you visited my website so i got here to go
    back the prefer?.I am trying to to find issues to enhance
    my website!I suppose its ok to make use of some of your concepts!!

  38. When I originally commented I appear to have clicked the -Notify me when new comments are added- checkbox and from now on each time a comment is added I receive four emails with the same comment.
    Perhaps there is a way you can remove me from
    that service? Many thanks!

  39. I was more than happy to find this web-site.I wished to thanks to your time for this wonderful learn!! I definitely having fun with each little little bit of it and I have you bookmarked to check out new stuff you weblog post.

  40. Please let me know if you’re looking for a article writer for your blog.
    You have some really good posts and I feel I would be a good asset.
    If you ever want to take some of the load off, I’d absolutely love to
    write some content for your blog in exchange for a link back to mine.
    Please shoot me an e-mail if interested. Thank you!

  41. Please let me know if you’re looking for a writer for your weblog.
    You have some really great articles and I
    think I would be a good asset. If you ever want to take some of the load off,
    I’d absolutely love to write some content for your blog in exchange for a link back
    to mine. Please send me an e-mail if interested. Thanks!

  42. My spouse and I absolutely love your blog and find
    many of your post’s to be exactly what I’m looking for.

    Does one offer guest writers to write content for yourself?
    I wouldn’t mind publishing a post or elaborating on many of the
    subjects you write concerning here. Again, awesome blog!

  43. Link exchange is nothing else but it is simply placing the other person’s website link on your page at proper place and other person will also do
    similar in favor of you.

  44. Very good blog! Do you have any suggestions for aspiring writers?

    I’m planning to start my own blog soon but I’m a little lost on everything.
    Would you advise starting with a free platform like WordPress
    or go for a paid option? There are so many options out there that I’m totally
    confused .. Any ideas? Kudos!

  45. I feel that is one of the such a lot important info for me.
    And i’m glad reading your article. However should statement on few normal things, The web site style is perfect, the
    articles is truly nice : D. Good process, cheers

  46. naturally like your website however you have
    to take a look at the spelling on quite a
    few of your posts. Several of them are rife with spelling issues and I find it very troublesome to inform the
    reality then again I will certainly come back again.

Leave a Reply

Your email address will not be published. Required fields are marked *