Tracking scroll position and notifying other components about it
Solution 1
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 );
}
}
Hint:
bootstrap(MyComponent, [
provide(PLATFORM_DIRECTIVES, {useValue: [TrackScrollDirective], multi:true})]);
makes the directive universal without adding it to every components directive: [...]
list.
Solution 2
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()
}
}
Solution 3
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.
Solution 4
I had a similar problem, i needed to scroll a div when user scroll a page, and solved my problem with the code below. At the component where you want to capture the scroll:
import { HostListener } from '@angular/core';
@ViewChild('curtain') divCurtain: ElementRef;
export class ComponentX {
@HostListener('window:scroll', ['$event']) onScrollEvent($event) {
console.log(window.pageYOffset);
this.divCurtain.nativeElement.style.top = window.pageYOffset.toString().concat('px');
}
ngOnInit(): void { }
}
Only this, i did not create any directive or other code.
This HostListener
is executed every time the user scroll the page, and i get the window.pageYOffset
to send to my div.
I hope it helps.
Shurik Agulyansky
Crazy about any type of technologies. Investing my nights learning and day using what's been learned. Passionate about problem-solving!!!
Updated on July 05, 2022Comments
-
Shurik Agulyansky almost 2 years
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).
- 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); } }
- Add it as a directive everywhere that uses it:
directives: [TrackScrollDirective]
- Add the attribute to each element we want to track the event:
<div class="col-md-12" track-scroll>
-
Shurik Agulyansky about 8 yearsBy the way, the option with
HostListener
does not work for some reason. Didn;t spend time digging into it. -
Günter Zöchbauer about 8 yearsI added another Plunker link utilizing
@HostListener()
-
Shurik Agulyansky about 8 yearsWell, I did exactly that, the only difference is that in my case it is a directive, not a component.
-
Toolkit over 7 yearsError in :0:0 caused by: self.context.doSomething is not a function
-
Günter Zöchbauer over 7 yearsWhen doing what? I can't reproduce with any of the two Plunker above.
-
András Szepesházi about 7 yearsFor browser cross-compatibility, use window.pageYOffset instead of document.body.scrollTop, see developer.mozilla.org/en-US/docs/Web/API/Window/pageYOffset and developer.mozilla.org/en-US/docs/Web/API/Window/scrollY
-
Tanasos about 7 years@GünterZöchbauer How to implement this as a service?
-
Günter Zöchbauer about 7 yearsYou would just need to use
window.addEventListener('scroll', this.doSomething.bind(this))
instead of@HostListener(...)
-
solstice333 almost 7 yearsThe angular2 documentation has an example of using @HostListener with a directive where the first argument of the decorator is an event tied to the host element i.e. the element that the directive is acting on. In your example, you have 'window:scroll' working from within a component instead of a host element. How did you figure it'd be 'window:scroll' (with the colon) instead of 'window.scroll' (with a period)? Were you able to find any doc on this somewhere?
-
Günter Zöchbauer almost 7 years
window:scroll
means Angular registers the event handler on thewindow
object. I don't thinkwindow.scroll
is valid at all. Perhaps emitting a custom event with the mamewindow.scroll
is possible, but that could as well be any other name -window.
has no special meaning here, whilewindow:
has. The later is for global event targets. I'm sure it is mentioned somewhere in the docs, but I easn't able to find it. -
Dominik about 4 yearsWindow scroll only works on the top level element body / html. How to listen to an element scroll? Or on the scrollTop property. Or another way to trigger if the element is in the view.
-
Günter Zöchbauer about 4 years
(scroll)="eventhandler"
on any element that is scrollable. -
Miquel Canal over 3 yearsThis is the correct answer for those who are looking for scroll event inside a DOM element that is not the window. In my case, I am tracking the scroll of a <div> nested down the DOM tree.
event.srcElement.scrollTop
works like a charm