5

I can't figure out how to detect when the mat-sidenav-container scrolls in angular material 2.

I would like to call a method on my component when a user scrolls. However when using the sidenav-container the scroll event is no longer fired on the window.

Instead an extra <mat-sidenav-content cdkscrollable></mat-sidenav-content> element is added and this is where the scroll bars are. How can I detect when scrolling occurs on this component if I can't directly access it to put an (scroll)="myMethod()" event listener on it.

Any ideas?

Chris
  • 564
  • 1
  • 7
  • 13

7 Answers7

4

Instead of calling a method on every scroll event you could create an Observable of scroll events and subscribe to it.

Using Observable

enum Direction {
    Up = 'Up',
    Down = 'Down'
}

ngAfterViewInit() {
    const content = document.querySelector('.mat-sidenav-content');
    const scroll$ = fromEvent(content, 'scroll').pipe(
      throttleTime(10), // only emit every 10 ms
      map(() => content.scrollTop), // get vertical scroll position
      pairwise(), // look at this and the last emitted element
      map(([y1, y2]): Direction => (y2 < y1 ? Direction.Up : Direction.Down)), // compare this and the last element to figure out scrolling direction
      distinctUntilChanged(), // only emit when scrolling direction changed
      share(), // share a single subscription to the underlying sequence in case of multiple subscribers
    );

    const goingUp$ = scroll$.pipe(
      filter(direction => direction === Direction.Up)
    );

    const goingDown$ = scroll$.pipe(
      filter(direction => direction === Direction.Down)
    );

    goingUp$.subscribe(() => console.log('scrolling up');
    goingDown$.subscribe(() => console.log('scrolling down');
}

For further explanation of the code above check out: https://netbasal.com/reactive-sticky-header-in-angular-12dbffb3f1d3

Community
  • 1
  • 1
frido
  • 13,065
  • 5
  • 42
  • 56
3

My current Version Angular 8
Solution 1:

  • Check if you content is placed inside the

        <mat-sidenav-container fullscreen> </mat-sidenav-container>
    
  • With fullscreen property above any of the window methods like window.scroll() and the properties like window.pageYOffset will not work

  • Remove fullscreen and try. It will workstrong text

Solution 2: You want to keep the property fullscreen

  • Check if you content is placed inside the

       <mat-sidenav-container fullscreen>
          <mat-sidenav></mat-sidenav> 
          <div>
             //your content
          </div>
       </mat-sidenav-container>
    
  • In above i did't put my content inside a mat-sidenav-content like below, since angular places it for you by default inside a mat-sidenav-content tag gives us the benefit of default behaviour

        <mat-sidenav-content>
           <div>
             //your content
          </div>
        </mat-sidenav-content>
    
  • import {MatSidenavModule} from '@angular/material/sidenav'; in app.module.ts and declare in imports array.

  • In your component in which you would like to get scroll event to do some stuff when the scroll position changes. Do the following

    import { Component, NgZone , OnInit } from '@angular/core';
    import { ScrollDispatcher } from '@angular/cdk/scrolling';
    import { CdkScrollable } from '@angular/cdk/scrolling';
    @Component({
      selector: 'app-scroll-top',
      templateUrl: './scroll-top.component.html',
      styleUrls: ['./scroll-top.component.css']
    })
    export class ExampleComponent implements OnInit {


      constructor (private scrollDispatcher: ScrollDispatcher, private zone: NgZone) {}

      ngOnInit() {
      }

      ngAfterViewInit() {
        this.scrollDispatcher.scrolled().
        subscribe((cdk: CdkScrollable)  => {
        this.zone.run(() => {
        //Here you can add what to happen when scroll changed
        //I want to display the scroll position for example
          const scrollPosition = cdk.getElementRef().nativeElement.scrollTop;
          console.log(scrollPosition);
        });
        });
      }
Guru Cse
  • 2,805
  • 2
  • 18
  • 15
1

Could you show me your css file? I also experienced the same problem, but due to the following css being applied to mat-sidenav-container.

height: 100vh;
1

Rather than letting Angular automatically insert the tag, include the side-nav-content tag in your HTML. Then you can add the listener to it.

<mat-sidenav-container>
  <mat-sidenav>
  </mat-sidenav>
  <mat-sidenav-content (scroll)="myScrollHandler($event)">
  </mat-sidenav-content>
</mat-sidenav-container>
jm22
  • 131
  • 3
  • 9
1

As soon as <mat-sidenav-content> has the cdkScrollable Directive, you can get it in your component thanks to @ViewChild decorator:

@ViewChild(CdkScrollable) scrollable: CdkScrollable;

Then you can observe the scroll behaviour by subscribing to the elementScrolled Observable:

this.scrollable.elementScrolled().subscribe(scrolled => console.log('scrolled', scrolled));
Benoît Plâtre
  • 596
  • 1
  • 5
  • 4
  • Why use `@ViewChild(CdkScrollable)` when the [documentation](https://material.angular.io/components/sidenav/overview#reacting-to-scroll-events-inside-the-sidenav-container) says to use `@ViewChild(MatSidenavContainer)`? Is there a difference? – krummens Jul 14 '20 at 20:49
  • 1
    There is no difference. I think it is more readable like that. If you want to retrieve a CdkScrollable component, it is clearer, I think, to reach it like that. But the most important is to cast it as CdkScrollable, to get its own properties, if you want to do something with its scrollable properties and methods. – Benoît Plâtre Jul 15 '20 at 01:30
0

You should use Material2 example "Sidenav with a FAB".

Please wrap scrollable content with div:

<mat-sidenav-container class="example-sidenav-fab-container">
  <mat-sidenav #sidenav mode="side" opened="true">
   ...
  </mat-sidenav>
  <div class="example-scrolling-content" (scroll)="handleScroll($event)">
    ... content ...
  </div>
</mat-sidenav-container>

and this should be your css:

.example-scrolling-content {
  overflow: auto;
  height: 100%;
}
0

Try adding fixedInViewport="true"

For more styling: [style.marginTop.px]="56"

Gour Gopal
  • 581
  • 7
  • 22