Tracking scroll position and notifying other components about it

I think the easiest way is each interested component listening to the scroll event.

  @Component({
    ...
    // alternative to `@HostListener(...)`
    // host: {'(window:scroll)': 'doSomething($event)'}
  })
  class SomeComponent {
    @HostListener('window:scroll', ['$event']) 
    doSomething(event) {
      // console.debug("Scroll Event", document.body.scrollTop);
      // see András Szepesházi's comment below
      console.debug("Scroll Event", window.pageYOffset );
    }
  }

plunker

Plunker using @HostListener()

Hint:

bootstrap(MyComponent, [
    provide(PLATFORM_DIRECTIVES, {useValue: [TrackScrollDirective], multi:true})]);

makes the directive universal without adding it to every components directive: [...] list.


I was forced to solve this differently because I needed to watch several scrolling elements on the window. I created a directive to watch the scroll position on an element:

@Directive({
  selector: '[scroll]'
})
export class ScrollDir {
  @Output() setScroll = new EventEmitter();
  private scroll: number;

  constructor(private el: ElementRef) { }

  @HostListener('scroll', ['$event'])
  scrollIt() { this.scroll = event.srcElement.scrollTop }

  reset() {  this.el.nativeElement.scrollTop = this.scroll }
}

Then on any any component containing a scroll element that needed this element I could @ViewChild the directive like this:

@Component({
  selector: 'parent',
  template: `
    <div class="container" scroll>
      // *ngFor=""...
    </div>
  `
})
export class ParentComp implements AfterViewChecked {

  @ViewChild(ScrollDir) scroll: ScrollDir;

  ngAfterViewChecked() {
    this.scroll.reset()
  }
}

Look at the source to ScrollService, as part of the angular documentation project.

The way they get the position is fromEvent(window, 'scroll')

You can then do something like this in a global service you inject into your component:

public readonly windowScroll$ = fromEvent(window, 'scroll').pipe(map(x => window.scrollY), startWith(0), distinctUntilChanged(), shareReplay(1));

The startWith(0) is needed because you may not get a scroll event until you actually scroll. You can add debouncing if needed.