Monitoring the current scroll position and updating other components on changes

Is there a way to easily monitor the scroll position of the browser and inform multiple components about it?

For example, I would like to dynamically change the classes of different elements on the page as the user scrolls. In the past, with older versions of Angular and jQuery, this was achievable with the help of plugins. While it is possible to implement this functionality using vanilla JavaScript and emitting events on application start, it can be messy and inefficient.

What are some alternative solutions to tackle this problem?


UPDATE (following suggestions):

Here is my attempt at solving this:

I created a new 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);
    }
}

Then I added an attribute to the main directive of my application:

<priz-app track-scroll>

Furthermore, I included 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]
})

Unfortunately, it did not yield the expected results...


ANOTHER UPDATE:

I moved the track-scroll attribute to one of the div elements within 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>

As a result, the application loads with a blank screen. Exciting times...


FINAL SOLUTION (that worked for me).

  1. Create 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. Include the directive in all components that require it:
directives: [TrackScrollDirective]
  1. Assign the attribute to each element that needs to track the event:
<div class="col-md-12" track-scroll>

Answer №1

The most straightforward method is for each interested component to listen for the scroll event.

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

View the example on Plunker

Plunker demonstrating the use of @HostListener()

Hint:

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

This makes the directive universally applicable without the need to add it to the directive: [...] list of every component.

Answer №2

I had to approach this problem in a unique way because I had to monitor multiple scrolling elements on the window. To address this, I developed a directive to track the scroll position on a specific element:

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

  constructor(private elementRef: ElementRef) { }

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

  resetScroll() {  this.elementRef.nativeElement.scrollTop = this.scrollPosition }
}

Then, in any component that included a scrolling element requiring this feature, I could use @ViewChild to access the directive like so:

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

  @ViewChild(ScrollDirective) scrollDirective: ScrollDirective;

  ngAfterViewChecked() {
    this.scrollDirective.resetScroll()
  }
}

Answer №3

Check out the documentation for the ScrollService in the Angular project.

The method they use to retrieve the position is fromEvent(window, 'scroll')

In a global service that you inject into your component, you can implement something similar to this:

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

The startWith(0) is important because you may not receive a scroll event until you begin scrolling. If necessary, you can add debouncing.

Answer №4

I encountered a similar issue where I needed a div to scroll along with the page. I was able to resolve this challenge using the following code snippet. When working with the component that requires the scroll functionality, include the following code:

import { HostListener } from '@angular/core';

@ViewChild('curtain') divCurtain: ElementRef;

export class ComponentY {
    @HostListener('window:scroll', ['$event']) onScrollEvent($event) {
        console.log(window.pageYOffset);
        this.divCurtain.nativeElement.style.top = window.pageYOffset.toString().concat('px');
    }

    ngOnInit(): void { }
}

It is important to note that I solely used the HostListener without creating any additional directives or codes. This listener is triggered whenever the user scrolls the page, allowing me to retrieve the window.pageYOffset and apply it to my div element.

I hope this solution proves to be beneficial to you.

Answer №5

When working in Angular, it is possible to monitor the scroll position within a specific div using viewchild events.

Start by adding a reference in the HTML code

<div #postion class="scroll-container">
  .......
  .......
</div>

Next, declare the reference in the TypeScript file using viewchild

@ViewChild('postion', { static: true }) _div: ElementRef;

Then, within the ngAfterViewInit function

 ngAfterViewInit() {
     fromEvent(this._div?.nativeElement, 'scroll').subscribe((e: any) =>  {
         this.scrollPosition = e?.target['scrollTop'];
            });
  }
 

Remember to import fromEvent from rxjs.

If you need to scroll to the saved position later on, you can use

      (this._div.nativeElement as HTMLElement).scrollTop = this.scrollPosition;

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What steps should be taken to find a particular route when a user logs in for the first time?

I am currently working on an application using Angular 2+, Node.js, and Express.js that includes registration and login authentication functionality. How can I direct first-time users to a specific route upon login, but for all subsequent logins have them ...

Retrieving information from a child modal window in an Angular parent component

In the scenario where there is data in two tables on the left and right sides, a modal popup window will open when a user clicks a link from the parent component. Upon selecting a radio button from the window, it should correspond to the selected link in t ...

Enhancing Web Service Calls with Angular 2 - The Power of Chaining

I am currently facing an issue where I need to make multiple web service calls in a sequence, but the problem is that the second call is being made before the .subscribe function of the first call executes. This is causing delays in setting the value of th ...

Two lines in ChartJS with distinct fill gradients for each

I am facing an issue with ChartJS version 2.6 in my Ionic 3/Angular 4 app. I have set up a line chart config with 2 datasets, but I am struggling to apply individual fill gradients to each line. What I am aiming for is something like this: https://i.stac ...

What is the best way to bring two useStyles files into a single TypeScript file?

I am having an issue with finding a declaration file for the module 'react-combine-styles' even after I installed it using npm install @types/react-combine-styles. import React, { useState } from "react"; import useStyles from "./u ...

"Creating a visual representation of models exchanged between the client and server through Rest

Currently, I am working on a project that involves client-server communication via rest API, with Angular 2 calling restful web services as the primary method. The client side is written in Typescript, which is a subset of JavaScript. My main challenge li ...

Troubleshooting Angular: Investigating why a component is failing to redirect to a different route

I am currently implementing a redirect to a new route upon logging in using the following code: this.router.navigate(['/firstPage']); Oddly enough, when my application is initially loaded, this redirection does not occur automatically after logi ...

Tips for sending data returned asynchronously from an Observable in Angular from a Child component to its Parent

I am facing a challenge passing Async data from child to parent component. When I try to access console.log(this.values) within the ngAfterViewInit() method in the parent component's HTML page load, it returns an empty value. However, upon clicking th ...

Angular 15 experiences trouble with child components sending strings to parent components

I am facing a challenge with my child component (filters.component.ts) as I attempt to emit a string to the parent component. Previously, I successfully achieved this with another component, but Angular seems to be hesitant when implementing an *ngFor loop ...

NiceScroll fails to function properly following the loading of AJAX content

My issue revolves around Nice Scroll. It functions properly, but fails to work when loading AJAX images. Strangely enough, it begins to work again when I resize the window or open Firebug. Is there a way to make this happen automatically? I attempted the ...

Error: The Select2 query service is not available

I am looking to enhance the search functionality for my select2 dropdown. My goal is to trigger a service call with the search parameters once 3 characters are typed into the search field. However, when I try to select an option from the dropdown, I encou ...

Webpack and incorporating bespoke scripts

Can someone please help me understand how webpack works? I'm currently working on an Angular 2 project with a webpack starter and I have some JavaScript scripts from AWS (my SDK for API Gateway). These are about 10 JS files that I currently have liste ...

issue encountered when implementing slick.js in angular 6

Trying to implement slick.js, a carousel framework, in an Angular project. Initially attempted 'npm install slick --save' and manually adding the downloaded files to scripts and styles JSON objects. When that failed, resorted to importing the C ...

Updating an Angular 2 project for the MEAN Stack development platform

A few weeks back, I embarked on an Angular2 project by following the "Tour of Heroes" tutorial. As I progressed, my project grew in complexity with routers, rest services, and hundreds of lines of code. Now, as I look to transition my project to the MEAN ...

Discover the latest techniques for incorporating dynamic datalist options in Angular 13 with Bootstrap 5

React 17 Tailwind CSS dynamiclist.component.html <div> <label for="{{ id }}" class="form-label">{{ label }}</label> <input class="form-control" list="{{ dynamicListOptions }}" [i ...

How to set up npm to utilize the maven directory format and deploy war files

Embarking on my very first pure front-end project, I decided to deploy it using Java/Maven. To begin, I set up a standard WAR project: │ package.json │ pom.xml │ tsconfig.json │ typings.json │ │ ├───src │ └───main ...

Angular modules are designed to repeat chunks of code in a

Whenever I scroll the page, my function pushes items to an array. However, I am facing an issue where the pushed items are repeating instead of adding new ones. Solution Attempt onScroll(): void { console.log('scrolled'); var i,j,newA ...

Issue with Vue router - Multiple calls to the "next" callback were detected within one navigation guard

I have incorporated Vue 3 with Vue router 4 and have implemented middleware functions that my routes must pass through. However, I am encountering an error in Vue that states: The "next" callback was invoked multiple times in a single navigation guard wh ...

Angular time-based polling with conditions

My current situation involves polling a rest API every 1 second to get a result: interval(1000) .pipe( startWith(0), switchMap(() => this.itemService.getItems(shopId)) ) .subscribe(response => { console.log(r ...

Retrieving an Observable within an Observable is a feature found in Angular 9

Seeking a solution to return an Observable nested within another Observable. Although I've tried using the pipe and map operators, it doesn't appear to be functioning correctly for me. What could be causing the issue? My development environment ...