Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tracking scroll position and notifying other components about it

Is there an easy way to track the browser scroll position and notify more than a single component about it?

Use case: On scroll I want to be able to change classes of various elements on the page based upon where I am. In a previous version of angular it was somewhat possible through a plugin (same for jQuery). Of course, there is the option of writing bare JS to initialize it on application start and emit an event, but that sounds dirty, and event emission is pretty expensive for this type of thing.

What are my options here?


UPDATE (after suggestions):

Here is what I tried:

I created a component:

import {Component} from "angular2/core";

@Component({
    selector: '[track-scroll]',
    host: {'(window:scroll)': 'track($event)'},
    template: ''
})

export class TrackScrollComponent {
    track($event) {
        console.debug("Scroll Event", $event);
    }
}

added an attribute to the main directive of an app:

<priz-app track-scroll>

and added the component as one of the providers in the top component:

import {TrackScrollComponent} from "../../shared/components/track-scroll.component";

@Component({
  selector: 'priz-app',
  moduleId: module.id,
  templateUrl: './app.component.html',
  directives: [ROUTER_DIRECTIVES, SecureRouterOutlet, AppHeader, TrackScrollComponent],
  providers: [AuthenticationService]
})

Still nothing...


ANOTHER UPDATE:

Moved track-scroll to one of the div elements of the main template:

<div class="container-fluid" track-scroll>
    <div class="row">
        <div class="col-md-12">
            <app-header></app-header>
            <secure-outlet signin="Login" unauthorized="AccessDenied"></secure-outlet>
        </div>
    </div>
</div>

And now the app loads with a completely empty screen. FUN FUN FUN...


FINAL SOLUTION (that worked for me).

  1. Define a directive:
import {Directive} from "angular2/core";

@Directive({
    selector: '[track-scroll]',
    host: {'(window:scroll)': 'track($event)'}
})

export class TrackScrollDirective {
    track($event: Event) {
        console.debug("Scroll Event", $event);
    }
}
  1. Add it as a directive everywhere that uses it:
directives: [TrackScrollDirective]
  1. Add the attribute to each element we want to track the event:
<div class="col-md-12" track-scroll>
like image 914
Shurik Agulyansky Avatar asked Apr 07 '16 06:04

Shurik Agulyansky


People also ask

How do you control the scroll position?

To get or set the scroll position of an element, you follow these steps: First, select the element using the selecting methods such as querySelector() . Second, access the scroll position of the element via the scrollLeft and scrollTop properties.

How does scroll position react detect?

To get scroll position with React, we can add a scroll listener to window . to create the scrollPosition state with the useState hook. Then we add the handleScroll function that takes the scroll position with the window.


3 Answers

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.

like image 61
Günter Zöchbauer Avatar answered Oct 09 '22 22:10

Günter Zöchbauer


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()
  }
}
like image 13
Nate May Avatar answered Oct 09 '22 20:10

Nate May


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.

like image 11
Simon_Weaver Avatar answered Oct 09 '22 21:10

Simon_Weaver