Reputation: 3723
Display a list of messages and scroll to the bottom when a new message is received, even when I am at the top. I would like to scroll fully bottom even with elements of different heights.
With virtual scroll, I have to set the [itemSize]
property, but for me it is not a static value:
Also, I am using ng-content
to insert a button from the parent to load previous messages. What I see is that, when _scrollToBottom
is invoked, instead of taking me to the bottom, it takes me to a bit higher. I suspect this is because of the different heights of elements inside virtual-scroll.
I have read this autosize scroll strategy issue from Angular: https://github.com/angular/components/issues/10113; but I am not sure this will solve my problem.
Any idea of what I could do will be welcome.
Codesandbox: https://codesandbox.io/s/angular-virtual-scroll-biwn6
Video with the error: https://gofile.io/d/8NG9HD
The solution given by Gourav Garg works. Simply by executing twice the scroll function.
I am doing this now:
private _scrollToBottom() {
setTimeout(() => {
this.virtualScrollViewport.scrollTo({
bottom: 0,
behavior: 'auto',
});
}, 0);
setTimeout(() => {
this.virtualScrollViewport.scrollTo({
bottom: 0,
behavior: 'auto',
});
}, 50);
}
I think it is not very elegant but works fine.
Upvotes: 10
Views: 15719
Reputation: 577
Angular Material v17 here. Although the main solution proposes two timeouts, it is achievable with a callable function:
// Access the CDK Virtual Scroll Viewport
@ViewChild(CdkVirtualScrollViewport) viewport: CdkVirtualScrollViewport;
viewportScrollToBottom(): void {
this.viewport.scrollTo({
bottom: 0,
behavior: 'smooth',
});
}
Upvotes: 0
Reputation: 850
My particular use case was different from the original post, but similar enough to say something. If you are trying to scroll to the bottom of the CDK's virtual scrolling viewport using scrollToIndex
, it isn't working, and you end up here, it might help you to know a few things.
ngOnInit
. (See this code.) So if you try to interact with it before it is finished initializing, it silently fails. (This feature request would at least provide an error when an empty viewport is scrolled.)If you call scrollToIndex
directly in ngAfterViewInit
on cdk-virtual-scroll-viewport
's parent component, that won't work. You have to wait a tick before using scrollToIndex
. See this Stackblitz. The important bits:
ngAfterViewInit(): void {
// does nothing
// this.viewPort.scrollTo({ bottom: 0, behavior: 'smooth' });
this.scrollToIndex$.next();
}
ngOnInit(): void {
this.subscriptions.add(
this.scrollToIndex$
.pipe(***-->delay(0)<--***)
.subscribe(() =>
this.viewPort.scrollTo({ bottom: 0, behavior: 'smooth' })
)
);
}
delay(0)
waits one tick of the Angular lifecycle, which seems to be enough to allow the viewport to finish itself up.
Upvotes: 3
Reputation: 568
There's an alternative to cdk scroll.
Here's a genric version of code to scroll to a given HTML element. It can be used as a service function in angular or as a function in javascript.
scroll(el: HTMLElement, behaviour: any = "smooth", block: any = "start", inline: any = "nearest") {
el.scrollIntoView({ behavior: behaviour, block: block, inline: inline })
}
Sample HTML:
<div class="scroll-to-top" [ngClass]="{'show-scrollTop': windowScrolled}">
Back to top<button mat-button mat-icon-button (click)="scrollToTop()">
<mat-icon>keyboard_arrow_up</mat-icon>
</button>
</div>
Sample Typescript component code:
@HostListener("window:scroll")
onWindowScroll() {
if (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop > 100) {
this.windowScrolled = true;
}
else if (this.windowScrolled && window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop < 10) {
this.windowScrolled = false;
}
}
scrollToTop() {
(function smoothscroll() {
var currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
if (currentScroll > 0) {
window.requestAnimationFrame(smoothscroll);
window.scrollTo(0, currentScroll - (currentScroll / 8));
}
})();
}
Upvotes: 1
Reputation: 2911
You can use if you are using cdk > 7
this.virtualScrollViewport.scrollToIndex(messages.length-1);
As this will move to the top of last item. You need to call scrollIntoView for that item.
this.virtualScrollViewport.scrollToIndex(this.numbers.length - 1);
setTimeout(() => {
const items = document.getElementsByClassName("list-item");
items[items.length - 1].scrollIntoView();
}, 10);
<cdk-virtual-scroll-viewport #virtualScroll style="height: 500px" itemSize="90">
<ng-container *cdkVirtualFor="let n of numbers">
<li class="list-item"> {{n}} </li>
</ng-container>
</cdk-virtual-scroll-viewport>
I have updated your sandbox
Upvotes: 7
Reputation: 51
Use can use scrollIntoView()
to scroll to the bottom of virtualScrollViewport
this.virtualScrollViewport.scrollIntoView(false);
refer https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
Upvotes: -1