Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/cdk/scrolling/scrolling.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,55 @@ This marks the element as a `Scrollable` and registers it with the `ScrollDispat
dispatcher, then, allows components to share both event listeners and knowledge of all of the
scrollable containers in the application.

#### Reacting to scroll events
`CdkScrollable` exposes an `elementScrolled` stream that emits whenever its element is scrolled.
Use it together with `measureScrollOffset`, which returns the scroll position relative to a given
edge of the element, to respond to the user scrolling. The stream completes when the directive is
destroyed.

<!-- example(cdk-scrollable-overview) -->

To react to scrolling in any of the scrollable containers in the application, inject the
`ScrollDispatcher` and subscribe to `scrolled`. It emits the `CdkScrollable` that was scrolled, or
`undefined` when the page itself is scrolled. If you only care about the containers around a
particular element, use `ancestorScrolled` instead, which only emits for those containers and the
page. `ancestorScrolled` looks up the containers at the time it is called, so call it in `ngOnInit`
or later, once the containers have been registered.

Both streams are throttled to one event every 20 milliseconds by default. You can change this by
passing a different time in milliseconds. Unsubscribe once you no longer need the events, because
the dispatcher keeps a global scroll listener as long as the stream has subscribers.

```ts
import {Component, DestroyRef, ElementRef, OnInit, inject} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {ScrollDispatcher} from '@angular/cdk/scrolling';

@Component({
selector: 'my-component',
template: '...',
})
export class MyComponent implements OnInit {
private _scrollDispatcher = inject(ScrollDispatcher);
private _elementRef = inject(ElementRef);
private _destroyRef = inject(DestroyRef);

ngOnInit() {
this._scrollDispatcher
.ancestorScrolled(this._elementRef)
.pipe(takeUntilDestroyed(this._destroyRef))
.subscribe(() => {
// Respond to the scroll event.
});
}
}
```

Scroll events are emitted outside of the Angular zone to avoid running change detection on every
scroll. Updating a class property in response to a scroll event won't refresh the view on its own,
so store the values that your template uses in signals, or call `ChangeDetectorRef.markForCheck`
after updating them.

### ViewportRuler
The `ViewportRuler` is a service that can be injected and used to measure the bounds of the browser
viewport.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.example-container {
height: 200px;
width: 200px;
margin-bottom: 8px;
overflow: auto;
border: 1px solid black;
}

.example-item {
height: 50px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<p>Scrolled {{scrollOffset()}}px from the top</p>

<div class="example-container" cdkScrollable>
@for (item of items; track item) {
<div class="example-item">{{item}}</div>
}
</div>

<button type="button" (click)="scrollToTop()" [disabled]="scrollOffset() === 0">
Back to top
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {AfterViewInit, Component, signal, viewChild} from '@angular/core';
import {CdkScrollable} from '@angular/cdk/scrolling';

/** @title Reacting to scroll events */
@Component({
selector: 'cdk-scrollable-overview-example',
templateUrl: 'cdk-scrollable-overview-example.html',
styleUrl: 'cdk-scrollable-overview-example.css',
imports: [CdkScrollable],
})
export class CdkScrollableOverviewExample implements AfterViewInit {
scrollable = viewChild.required(CdkScrollable);
items = Array.from({length: 50}).map((_, i) => `Item #${i}`);
scrollOffset = signal(0);

ngAfterViewInit() {
const scrollable = this.scrollable();

// Scroll events are emitted outside of the Angular zone. Updating a signal
// lets Angular know that the view needs to be refreshed.
scrollable.elementScrolled().subscribe(() => {
this.scrollOffset.set(Math.round(scrollable.measureScrollOffset('top')));
});
}

scrollToTop() {
this.scrollable().scrollTo({top: 0});
}
}
1 change: 1 addition & 0 deletions src/components-examples/cdk/scrolling/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export {CdkScrollableOverviewExample} from './cdk-scrollable-overview/cdk-scrollable-overview-example';
export {CdkVirtualScrollAppendOnlyExample} from './cdk-virtual-scroll-append-only/cdk-virtual-scroll-append-only-example';
export {CdkVirtualScrollContextExample} from './cdk-virtual-scroll-context/cdk-virtual-scroll-context-example';
export {CdkVirtualScrollCustomStrategyExample} from './cdk-virtual-scroll-custom-strategy/cdk-virtual-scroll-custom-strategy-example';
Expand Down
Loading