8

When using the cdk virtual viewport, need to set the height of the viewport

.example-viewport {
  height: 800px;
  width: 100%;
  border: 1px solid black;
}
<cdk-virtual-scroll-viewport class="example-viewport">
  <div *cdkVirtualFor="let item of items" class="example-item">{{item}}</div>
</cdk-virtual-scroll-viewport>

But i want the cdk-virtual-scroll-viewport to have wrap the content items if it is not reaching max height to appear scrollbar. But the viewport is not working with max-height.

If there is no horizontal scrollbar, then the viewport sets with height to maximum height is ok. But in my current design UI, i need to show the horizontal scrollbar because lots of content columns like below attached image.

enter image description here

Then the scroll bar is far below due to the height of the viewport. The row items will increase by time, but before the items are increased to max height, i want the horizontal scrollbar wrap to the content height, but currently seems not able to achieve.

The reason i don't use mat-table is that the i want to support infinite scrolling and render the items that fit to screen. Mat-table doesn't support this, if i continue scroll down and request data, the rows items increased in the template and impact on performance.

Anyone has better suggestion?

Thanks a lot.

4 Answers 4

8

I got a fix which considers the number of elements in the list to set the container height. It calculates the amount of height for the container until it reaches the final height value. Follow these steps and let me know.

1. Keep a reference of cdk-virtual-scroll-viewport in your component

We need this to be able to call checkViewportSize later and make CdkVirtualScrollViewport to recalculate its internal sizes.

Component

@ViewChild('scrollViewport')
private cdkVirtualScrollViewport;

Template

<cdk-virtual-scroll-viewport class="example-viewport" #scrollViewport>
...
</cdk-virtual-scroll-viewport>

2. Calculate the list container height based on the number of elements in the list

Component

calculateContainerHeight(): string {
  const numberOfItems = this.items.length;
  // This should be the height of your item in pixels
  const itemHeight = 20;
  // The final number of items you want to keep visible
  const visibleItems = 10;

  setTimeout(() => {
    // Makes CdkVirtualScrollViewport to refresh its internal size values after 
    // changing the container height. This should be delayed with a "setTimeout"
    // because we want it to be executed after the container has effectively 
    // changed its height. Another option would be a resize listener for the 
    // container and call this line there but it may requires a library to detect 
    // the resize event.

    this.cdkVirtualScrollViewport.checkViewportSize();
  }, 300);

  // It calculates the container height for the first items in the list
  // It means the container will expand until it reaches `200px` (20 * 10)
  // and will keep this size.
  if (numberOfItems <= visibleItems) {
    return `${itemHeight * numberOfItems}px`;
  }

  // This function is called from the template so it ensures the container will have 
  // the final height if number of items are greater than the value in "visibleItems".
  return `${itemHeight * visibleItems}px`;
}

Template

<div [style.height]="calculateContainerHeight()">
  <cdk-virtual-scroll-viewport class="example-viewport" #scrollViewport>
    <div *cdkVirtualFor="let item of items" class="example-item">{{item}}</div>
  </cdk-virtual-scroll-viewport>
</div>

It should be all. You only need to tweak itemHeight and visibleItems in the function according to your circumstances to get the result you're expecting.

2
  • 1
    this solution worked for me and it is calling this code recursively and causing the performance issue. is there is a way to prevent it. Commented Nov 21, 2022 at 6:32
  • [style.height.px]="(transactionLines.length > 5 ? 52*5 : 52*transactionLines.length) + (showHeaders ? 56 : 0)" Commented Sep 4, 2023 at 12:36
0

We can't use the max-height property with cdk-virtual viewport, one solution is to calculate the height based on the content(each item height multiplied by total number of items),

Another quick workaround which we can apply>,

When the content i.e(number of items) in the viewport is less which causes no scroll bar to take effect, then use a separate div (a normal div which loops through the items and where we can set the max-height as needed(if needed , as in normal div if we loop through items its height will be streched automatically, so basically no need to use max-heigth on this separate div)) and when the number of items is high enough to scroll bar to take effect then use the cdk-virtual viewport

here i wanted a scrolling bar when items are more then 3 , and that scrolling should be virtual scrolling, code :

<div *ngIf="serviceList && serviceList.length <=3">
    <ng-container *ngFor="let serviceItem of serviceList; let i = index;">
        <div class=" word-break pointer-default order-item-card">
            <order-service-item [serviceItem]="serviceItem" [headerData]="headerData" [orderItem]="msOrderItem"
                (updatePayloadData)="updateServicePayloadData($event)" [addBorder]="true" [recordIndex]="i"
                (selectionToggle)="isVariantServiceAdded('chkBoxEventTrigger', $event)"></order-service-item>
        </div>
    </ng-container>
</div>

<cdk-virtual-scroll-viewport itemSize="131" class="example-viewport" *ngIf="serviceList && serviceList.length > 3">
    <div *cdkVirtualFor="let serviceItem of serviceList; let i = index;"
        class="word-break pointer-default order-item-card">
        <order-service-item [serviceItem]="serviceItem" [headerData]="headerData" [orderItem]="msOrderItem"
            (updatePayloadData)="updateServicePayloadData($event)" [addBorder]="true" [recordIndex]="i"
            (selectionToggle)="isVariantServiceAdded('chkBoxEventTrigger', $event)">
        </order-service-item>
    </div>
</cdk-virtual-scroll-viewport>

0

You can use AfterViewChecked to recalculate virtual scroll.

Template

<cdk-virtual-scroll-viewport itemSize="40" [style]="{'height': data.items?.length | getHeight }" (scrolledIndexChange)="nextBatch()"> 
...
</cdk-virtual-scroll-viewport>

I have pipe to limit height.

Pipe

@Pipe({
  name: 'getHeight',
  standalone: true,
})
export class GetHeightPipe implements PipeTransform {
  public transform(count?: number): string {
    count ??= 0;
    return count > 10 ? '200rem' : `${count * 20}rem`;
  }
}

And creme de la creme.

Component

@Component({...})
export class NameComponent implements AfterViewChecked {
  @ViewChild(CdkVirtualScrollViewport) public viewport!: CdkVirtualScrollViewport;

  public ngAfterViewChecked(): void {
      this.viewport.checkViewportSize();
  }
}

1
  • "creme de la creme." is un peu français non ? :thonk:
    – Elikill58
    Commented Mar 26 at 22:22
-1
@Component({
...
  changeDetection: ChangeDetectionStrategy.OnPush,
})

That stops the recursiveness

Not the answer you're looking for? Browse other questions tagged or ask your own question.