5
votes

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?

7
I would like to know this as well! - Willwsharp

7 Answers

3
votes

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

2
votes

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);
        });
        });
      }
1
votes

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
votes

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>
1
votes

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));
0
votes

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
votes

Try adding fixedInViewport="true"

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