Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

virtual-scroll: re-render when the viewport or content size changes #10117

Open
mmalerba opened this issue Feb 23, 2018 · 23 comments
Open

virtual-scroll: re-render when the viewport or content size changes #10117

mmalerba opened this issue Feb 23, 2018 · 23 comments
Labels
area: cdk/scrolling feature This issue represents a new feature or feature request rather than a bug or bug fix P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent

Comments

@mmalerba
Copy link
Contributor

Add support for automatically re-rendering the list when the viewport or content size changes such that not enough content is rendered. We should also expose a public method to allow users to trigger this manually

@mmalerba mmalerba self-assigned this Feb 23, 2018
@mmalerba mmalerba added the feature This issue represents a new feature or feature request rather than a bug or bug fix label Feb 23, 2018
@amcdnl
Copy link
Contributor

amcdnl commented May 17, 2018

I ended up implementing this in my usages:

    fromEvent(window, 'resize')
      .pipe(
        distinctUntilChanged(),
        debounceTime(10),
        sampleTime(0, animationFrameScheduler),
        takeUntil(this._destroy$)
      ).subscribe(() => this.onResize());

@grant77
Copy link

grant77 commented Aug 25, 2018

Here is a temporary and crappy workaround. Probably needs a throttle, debounce, or something:

const ngOnInit = CdkVirtualScrollViewport.prototype.ngOnInit;
CdkVirtualScrollViewport.prototype.ngOnInit = function () {
ngOnInit.apply(this, arguments);

this['_resizeSubscription'] = fromEvent(window, 'resize')
.subscribe(() => this.checkViewportSize());
}

const ngOnDestroy = CdkVirtualScrollViewport.prototype.ngOnDestroy;
CdkVirtualScrollViewport.prototype.ngOnDestroy = function () {
ngOnDestroy.apply(this, arguments);
this['_resizeSubscription'].unsubscribe();
}

@jlowcs
Copy link

jlowcs commented Dec 14, 2018

Here's how I temporarily fixed it, waiting for the official fix:

@Directive({
	selector: 'cdk-virtual-scroll-viewport',
})
export class CdkVirtualScrollViewportPatchDirective implements OnInit, OnDestroy {
	protected readonly destroy$ = new Subject();

	constructor(
		@Self() @Inject(CdkVirtualScrollViewport) private readonly viewportComponent: CdkVirtualScrollViewport,
	) {}

	ngOnInit() {
		fromEvent(window, 'resize')
			.pipe(
				debounceTime(10),
				takeUntil(this.destroy$),
			)
			.subscribe(() => this.viewportComponent.checkViewportSize())
		;
	}

	ngOnDestroy() {
		this.destroy$.next();
		this.destroy$.complete();
	}
}

@Tyler-V
Copy link

Tyler-V commented Dec 28, 2018

Still no fix for this? This is one of the first things I noticed when porting my library over to this virtual scrolling

@trungkmy
Copy link

Any solution???

@lobsang70
Copy link

The virtual scrolling feature is so useful and I think it should manage the container resizing.

@jpirie
Copy link

jpirie commented Mar 13, 2019

Also hit this here using a variation of the linked issue #13981 (same root cause - viewport size should not be computed when the tab containing the virtual scrolling component is not activated).

In the meantime an application-only workaround for tab-based use could be to wrap the content under an *ngIf, if the tab is not activated so viewport calculation is not completed prematurely. Still, the above workarounds are preferable, so will try that first. Thanks to @grant77 and @jlowcs for that snippet 👍.

@digimezzo
Copy link

digimezzo commented Mar 14, 2019

@jlowcs's Directive works great to solve the window resizing issue. It, however, doesn't solve the issue when switching tabs when the virtual scrolling component is used in a mat-tab component. I'll need to figure out a similar mechanism which triggers checkViewportSize() on tab change.

@rodent129
Copy link

@jlowcs's Directive works great to solve the window resizing issue. It, however, doesn't solve the issue when switching tabs when the virtual scrolling component is used in a mat-tab component. I'll need to figure out a similar mechanism which triggers checkViewportSize() on tab change.

I did the following workaround as below:
On tabChangeAction => I trigger
tabChangeAction(index: number) {
this.cityTab.onVisible(true);
}

In the cityTab component ,if tab is visible, then dispatch resize event to trigger for checkViewportSize.
onVisible(flag: boolean) {
if (flag) {
window.dispatchEvent(new Event('resize'));
}
}

@r-hannuschka
Copy link

r-hannuschka commented Apr 18, 2019

@jlowcs first this is a good solution, but i dont see any reason angular cdk not handle this by its own should be a bug / error. If u have multiple Directives / Components or whatever on page which should react on a window resize u have to register multiple Window.Resize Events.

I think it is better to create 1 Resize Event and share this Event through Observable with all who are interested in this. So u will allways have only 1 resize event and this should be better in my opinion.

I create a window resize service for my own projects to register only one listener which will be shared.

And could registered like this, and at this point it is very easy to update cdk-virtual-scroll-viewport

export class MyComponent {

    constructor(
        private windowResize: WindowResize
    ) {
    }

    public ngOnInit() {
        this.windowResize.onChange()
            .pipe( takeUntil( this.destroy$ ) )
            .subscribe(() => {
                // window size has changed
            });
    }

@KimAlexander
Copy link

KimAlexander commented Jul 16, 2019

Still waiting for official solution

@Ploppy3
Copy link

Ploppy3 commented Aug 7, 2019

@vladjerca
Copy link

We've implemented an extension directive to handle these situations, easily reusable and doesn't require pollution of the parent controller class.

https://github.com/UiPath/angular-components/blob/master/projects/angular/directives/ui-virtual-scroll-viewport-resize/src/ui-virtual-scroll-viewport-resize.directive.ts

@mattcwebster
Copy link

Found a super easy, albeit hacky way to fix this. I've found you don't have to have the exact size of the itemSize property correct, if you change the value when the viewport changes, then it will refresh the number to display... it would be even easier but I had to use setTimeout to avoid the dreaded expression has changed after view checked.

template

<cdk-virtual-scroll-viewport [itemSize]="itemSize" 

component

export class VirtualScrollProfilesComponent implements OnInit {

  virtualPersons = [];
  itemSize = 50;

  ngOnInit() {
    this.determineItemSize();
  }


  private determineItemSize() {
    setTimeout(() => {
      this.itemSize = 50 - this.windowService.boostrapColumns;
    })
  }

  /**
   * resize columns on windowResize
   * @param event resize event
   */
  @HostListener('window:resize')
  onResize() {
    this.determineItemSize();
  }


}

@stefanholzapfel
Copy link

stefanholzapfel commented Apr 1, 2020

My virtual scroll component is sitting inside an expansion panel which changes the size of the viewport independent from the window size.

For everybody else facing that problem:

I ended up using css-element-queries and connecting the CdkVirtualScrollViewport to the ResizeSensor in the AfterViewInit hook. Now the viewport can be rerenderd on any size change, wherever it comes from.

@mmalerba mmalerba added the needs triage This issue needs to be triaged by the team label May 20, 2020
@crisbeto crisbeto added area: cdk/scrolling P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent and removed needs triage This issue needs to be triaged by the team labels May 25, 2020
@PigBoT
Copy link

PigBoT commented Jun 29, 2020

@jlowcs's Directive works great to solve the window resizing issue. It, however, doesn't solve the issue when switching tabs when the virtual scrolling component is used in a mat-tab component. I'll need to figure out a similar mechanism which triggers checkViewportSize() on tab change.

I did the following workaround as below:
On tabChangeAction => I trigger
tabChangeAction(index: number) {
this.cityTab.onVisible(true);
}

In the cityTab component ,if tab is visible, then dispatch resize event to trigger for checkViewportSize.
onVisible(flag: boolean) {
if (flag) {
window.dispatchEvent(new Event('resize'));
}
}

Dispatching the 'resize' event is working well for my use case.
window.dispatchEvent(new Event('resize'));

@MikeJerred
Copy link
Contributor

I am running into this issue because my CSS is causing the size to change, thus the window resize event is not fired. I am using a ResizeObserver workaround:

const resized$ = new Subject<void>();
const resizeObserver = new ResizeObserver(() => resized$.next());

resized$
  .pipe(throttleTime(20, undefined, { leading: true, trailing: true }), takeUntil(this.destroyed$))
  .subscribe(() => viewport.checkViewportSize());

resizeObserver.observe(viewport.elementRef.nativeElement);

this.destroyed$.subscribe(() => resizeObserver.disconnect());

Probably whatever code is causing the viewport to run checkViewportSize when a window resize event happens could be replaced with this.

@sokolej79
Copy link

Any news about this issue? Still not working with mat-tab when tab not active. Items don't resize vertically full height.

@malvinpatrick
Copy link

malvinpatrick commented Aug 20, 2023

I have tried using cdk virtual port, and I got same issues in Angular 16.1.0. But I found the root cause, it was because the cdk viewport wrapper doesn't work properly
image

And in my case, I found another CSS (outsite my component but makes an impact for my current component) that set cdk-virtual-scroll-content-wrapper height to 100%,
::ng-deep .cdk-virtual-scroll-content-wrapper { height: 100%; }

After I delete that CSS, and added checkViewportSize everytime active mat-tab changed into current tab that have cdk viewport
this.viewport?.checkViewportSize();
viewport works properly.

@WMOH-DEV
Copy link

an issue from 2018 still on and no solutions !!!!!!!!!!!!!!!!!!!

@AJ1062910
Copy link

still no solution ??

@AJ1062910
Copy link

try do this:
fromEvent(window, 'resize')
.pipe(
debounceTime(100),
takeUntilDestroyed(),
)
.subscribe(() => this.viewportComponent.checkViewportSize());

@kaalvoetjaco
Copy link

@jlowcs's Directive works great to solve the window resizing issue. It, however, doesn't solve the issue when switching tabs when the virtual scrolling component is used in a mat-tab component. I'll need to figure out a similar mechanism which triggers checkViewportSize() on tab change.

I modified the directive to also check for visibility changes in the cdk-virtual-scroll-viewport. When a mat-tab is not the active tab all child elements are hidden. Listening for visibility changes when the tab becomes active seems to work well.

import { Directive, ElementRef, Inject, OnDestroy, OnInit, Self } from '@angular/core';
import { debounceTime, fromEvent, Subject, takeUntil } from 'rxjs';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';

@Directive({
  selector: 'cdk-virtual-scroll-viewport-resize-aware',
  standalone: true
})
export class CdkVirtualScrollViewportHeightAwareDirective implements OnInit, OnDestroy {
  protected readonly destroy$: Subject<void> = new Subject();
  private visibilityObserver!: IntersectionObserver;

  constructor(
    @Self() @Inject(CdkVirtualScrollViewport) private readonly viewportComponent: CdkVirtualScrollViewport,
    private elementRef: ElementRef
  ) {}

  ngOnInit() {
    // Handle window resize events
    fromEvent(window, 'resize')
      .pipe(
        debounceTime(50),
        takeUntil(this.destroy$)
      )
      .subscribe(() => {
        console.debug('Checking viewport size after window resize');
        this.viewportComponent.checkViewportSize();
      });

    this.visibilityObserver = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          console.debug('Element became visible');
          this.viewportComponent.checkViewportSize();
        }
      });
    });

    this.visibilityObserver.observe(this.elementRef.nativeElement);
  }

  ngOnDestroy() {
    this.visibilityObserver.disconnect();
    this.destroy$.next();
    this.destroy$.complete();
  }
}

xetrics added a commit to xetrics/components that referenced this issue Aug 29, 2024
Adds support for automatically re-calculating the viewport's size if content size changes.

Partially implements angular#10117
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: cdk/scrolling feature This issue represents a new feature or feature request rather than a bug or bug fix P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
Projects
None yet
Development

No branches or pull requests