15

The Angular Material CDK provides a Directive CdkScrollable, which allows you to listen to ScrollEvents of a specific container.
I am now trying to access the CdkScrollable of the MatSidenavContent, which is added by default.
However my @ViewChild(CdkScrollable) and @ContentChild(CdkScrollable) are always undefined.

My Component looks something like this:

<mat-sidenav-container>
    <mat-sidenav>Sidenav content</mat-sidenav>
    <div>Main content</div>
</mat-sidenav-container>

The resulting DOM looks something like this:

<mat-sidenav-container>
    <div class="mat-drawer-backdrop"></div>
    <div tabindex="-1" class="cdk-visually-hidden cdk-focus-trap-anchor"></div>
    <mat-sidenav>Sidenav content</mat-sidenav>
    <mat-sidenav-content cdkScrollable>
          <div>Main content</div>
    </mat-sidenav-content>
</mat-sidenav-container>

The mat-sidenav-content Component, which is generated automatically, uses a CdkScrollable-Directive, which I need to access.
My question is now:
Is it possible to access that Directive and if so, how?

BuZZ-dEE
  • 6,075
  • 12
  • 66
  • 96
Robert P
  • 9,398
  • 10
  • 58
  • 100
  • It's funny that the material docs show this as an example but no matter what i try, ALL NIGHT, it does not work. – j_walker_dev Mar 14 '18 at 10:59
  • @j_walker_dev I opened an Issue on @angular/material some time ago and the `CdkScrollable` instance is now accessible. See [here](https://github.com/crisbeto/material2/blob/8187a0c6896e4e3ea2a6748d90724ac3c880cd60/src/lib/sidenav/sidenav.md#reacting-to-scroll-events-inside-the-sidenav-container) – Robert P Mar 14 '18 at 11:03
  • 1
    Thanks for the help Springbua. I was never able to get the `this.sidenavContainer.scrollable` to be anything other than undefined. I had to instead use only `CdkScrollable`. @ViewChild(MatSidenavContent) still to this day does not work for me trying to get the scroll. It is very weird. Thanks again! I am glad you got it to work! – j_walker_dev Mar 19 '18 at 23:26

2 Answers2

22
  1. Add to your app module imports: ScrollDispatchModule.
  2. Add cdkScrollable to your mat-sidenav-content:

<mat-sidenav-content cdkScrollable> </mat-sidenav-content>

  1. In your root component:

a) inject ScrollDispatcher from @angular/cdk/overlay and subscribe to scrolling:

constructor(public scroll: ScrollDispatcher) {

    this.scrollingSubscription = this.scroll
          .scrolled()
          .subscribe((data: CdkScrollable) => {
            this.onWindowScroll(data);
          });
}

c) do something when scrolling, e.g. check the offset

private onWindowScroll(data: CdkScrollable) {
    const scrollTop = data.getElementRef().nativeElement.scrollTop || 0;
    if (this.lastOffset > scrollTop) {
      // console.log('Show toolbar');
    } else if (scrollTop < 10) {
      // console.log('Show toolbar');
    } else if (scrollTop > 100) {
      // console.log('Hide toolbar');
    }

    this.lastOffset = scrollTop;
  }

Documentation: https://material.angular.io/cdk/scrolling/api

Update Angular 9 :

Use import {ScrollingModule} from '@angular/cdk/scrolling', ScrollDispatchModule is deprecated

BuZZ-dEE
  • 6,075
  • 12
  • 66
  • 96
Sebastian Denis
  • 928
  • 10
  • 20
6

I opened an Issue on @angular/material some time ago and they now expose the CdkScrollable-Instance.
To use it, you need to access the MatSidenavContainer using @ViewChild(MatSidenavContainer. This instance has a public member scrollable, which is the CdkScrollable instance.
An example can be found here

Edit: As the example is not very complete and a few people are having difficulties implementing it, I'll write my own example here:

HTML:

<mat-sidenav-container>
    <mat-sidenav #sidenav>
        Sidenav Content
    </mat-sidenav>
    <div>
        Main Content
    </div>
</mat-sidenav-container>

TypeScript:

import { Component, AfterViewInit, ViewChild } from '@angular/core';
import { MatSidenavContainer } from '@angular/material';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements AfterViewInit  {
  @ViewChild(MatSidenavContainer) sidenavContainer: MatSidenavContainer;

    constructor() {
    }

    ngAfterViewInit() {
      console.log(this.sidenavContainer.scrollable);
    }
}

Important:

  1. Don't use <mat-sidenav-content>. This tag is generated automatically and it has the cdkScrollable directive attached to it. If you use <mat-sidenav-content> in your own template, the scrollable will be undefined.
  2. Use AfterViewInit instead of OnInit. As much as I know, @ViewChild is resolved in AfterViewInit, OnInit is probably too early.
Robert P
  • 9,398
  • 10
  • 58
  • 100
  • I can't get it to work. Here my [example](https://stackblitz.com/edit/sidenavcontainer-scrollable-elementscrolled). I also found this [issue](https://github.com/angular/material2/issues/10884) on @angular/material. – stevo May 23 '18 at 19:44
  • 1
    @stevo I edited my answer. The problem in your example is, that you wirte `` yourself. This tag is automatically added by angular material and includes the `cdkScrollable`. In your example this is missing in the final HTML (I have seen that in the developer tools). Also you are using `ngOnInit`, but you should use `AfterViewInit`, if you are working with `ViewChild`. – Robert P May 24 '18 at 06:48
  • 1
    Thank you so much @Springrbua! I made a new [example](https://stackblitz.com/edit/sidenavcontainer-scrollable-elementscrolled-1) on Stackblitz. Now I can subscribe to `this.sidenavContainer.scrollable.elementScrolled()` but nothing happens. – stevo May 24 '18 at 16:51
  • Take a look at https://material.angular.io/cdk/scrolling/api#CdkScrollable The API of CdkScrollable should help. If not, feel free to create a new question and let me know about it. – Robert P May 24 '18 at 16:57
  • #1 under important just saved my mental state. thank you – john Jul 17 '18 at 21:43