Angular 2: Capturing scroll events from the parent element within a Directive

One of the challenges I encountered is with a directive called [appInvalidField] that functions like a custom tooltip for validation purposes. To ensure it appears above everything else within dialogs, I attach it to the body and position it near the relevant field.

The issue arises when scrolling comes into play. I realized that I need to dynamically adjust the tooltip's position based on scroll events triggered by the form. The tricky part is figuring out how to achieve this adjustment within the directive file without altering the existing HTML structure. Below is an example of how the directive is used in a form:

<form #ngForm="ngForm" [formGroup]="form" (ngSubmit)="onSave()">
  <div class="form--edit">
    <div class="form__group p-grid">
         <label class="p-col-12 form__label">{{'substance.IUPACName-title' | translate}}</label>
         <div appInvalidField="names">
               <span *appInvalidFieldType="'required'" [translate]="'substance.IUPACName-field-required'"></span>
                <span *appInvalidFieldType="'maxlength'"
                                [translate]="'substance.IUPACName-field-maxlength'"></span>
          </div>
          <input class="p-col" pInputText [maxLength]="formService.maxLength" appAutofocus  formControlName="names" />
    </div>
  </div>
</form>

Answer №1

Presented here is a custom directive that tracks all scroll events from the current position upwards:

@Directive({
    selector: '[allParentsScroll]',
})
export class AllParentsScrollDirective implements OnInit {
    @Output('allParentsScroll')
    readonly allParentsScroll$: Observable<Event>;

    private readonly ready$ = new Subject<void>();

    constructor(
        @Inject(ElementRef) {nativeElement}: ElementRef<Element>,
    ) {
        const eventTargets: EventTarget[] = [window, nativeElement];

        while (nativeElement.parentElement) {
            nativeElement = nativeElement.parentElement;
            eventTargets.push(nativeElement);
        }

        const allScroll$ = merge<Event>(
            ...eventTargets.map<Observable<Event>>(element => fromEvent(element, 'scroll')),
        );

        this.allParentsScroll$ = this.ready$.pipe(swithMapTo(allScroll$));
    }

    ngOnInit() {
        // Initialize the listener once everything is set up
        this.ready$.next();
    }
}

In essence, this directive traverses the DOM structure upwards, attaching scroll event listeners to each container and consolidating them into a single stream. I utilize it for handling similar scenarios, however there's a known limitation where querying any DOM positioning information during fast scrolling can cause delayed updates. To mitigate this, one solution is to position the element absolutely instead of fixed, as this approach minimizes recalculations triggered by body scrolls in most instances.

Answer №2

For a more sophisticated approach, consider utilizing the IntersectionObserver, which is demonstrated in this insightful post: How to check if element is visible after scrolling?

To implement this with a directive, you can create something similar to the following:

@Directive({
  selector: '[fixedscroll]'
})
export class FixedscrollDirective{
@Input() windowOnly = false;
constructor(
    @Inject(ElementRef) {nativeElement}: ElementRef<Element>,
) {
    var observer = new IntersectionObserver(onIntersection, {
      root: null,   // default is the viewport
      threshold: .5 // percentage of target's visible area. Triggers "onIntersection"
    })

    // callback is called on intersection change
    function onIntersection(entries, opts){
      entries.forEach(entry =>
        entry.target.classList.toggle('visible', entry.isIntersecting)
      )
    }

    // Use the observer to observe an element
    observer.observe( nativeElement )
}
}

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

Troubleshooting the lack of success in enhancing global scope within Typescript

Currently, I am working on a microservices application where I have two very similar services that use practically the same packages. To perform some testing, I decided to add a function to the global scope and modified it slightly to prevent any TypeScrip ...

Using TypeScript to define a constant array as a type

I've hit a roadblock in my typescript project while trying to define a suitable type. Here's the setup: Within my project, I have the following constant: export const PROPERTYOPTIONS = [ { value: "tag", label: "Tag" }, { ...

What is the best way to programmatically define the value for the MaterialUI grid size property using TypeScript?

Is there a way to dynamically pass a value to the Grid size props like XL in TypeScript? For instance, *Update for further clarification import Grid, { GridSize } from "@material-ui/core/Grid"; let value: GridSize = 12/4; xl={value} Error: Type &apos ...

Why is it that when I try to create a table using the "Create Table" statement, I keep getting an error saying "Near '(': syntax error"?

Error : There seems to be a syntax error near "(". Here is the SQL statement causing the issue: CREATE TABLE IF NOT EXISTS tickets ( numero INTEGER PRIMARY KEY AUTOINCREMENT, identifier VARCHAR(4) NOT NULL, subject VARCHAR(150) NOT NULL, ...

Mastering regular expressions in TypeScript

My goal is to perform linting on staged files that are either .ts or .tsx and located within the src folder. I am aware that for selecting all js files one can use "*.js": [--list of commands--] inside the lint staged property. I'm curious to learn m ...

Definition of generic with recursive immutability

I created a type definition to ensure immutability of types and their properties all the way down. However, when I attempt to use this with a generic type, the compiler claims that the types do not overlap, preventing me from casting an object as immutable ...

Display the concealed mat-option once all other options have been filtered out

My current task involves dynamically creating multiple <mat-select> elements based on the number of "tag types" retrieved from the backend. These <mat-select> elements are then filled with tag data. Users have the ability to add new "tag types, ...

Tips for incorporating state properties into a component

Currently engrossed in a project revolving around state management for individual components utilizing Angular 7 and NGRX. The challenge at hand is to ensure scalability of the implementation, allowing multiple uses while maintaining independence. Thus fa ...

What is the best way to send an array of locationIds to the getLocationData service?

Seeking Assistance on Sending an Array of Location IDs to a Service I currently have an array consisting of location IDs. locationArr=[40871, 60009, 38149, 40868, 43240, 15299, 53897, 40976, 38151, 23183, 38152, 78579, 23180, 40977, 23176, 39565, 40884, ...

The Nestjs ClientMqtt now has the capability to publish both pattern and data to the broker, as opposed to just sending

I am currently utilizing Nestjs for sending data to a Mqtt Broker. However, I am facing an issue where it sends both the pattern and data instead of just the data in this format: { "pattern": "test/test", "data": " ...

Updating text inputs in Angular can be done more efficiently using Angular Update

I need to make adjustments to an Angular application so that it can run smoothly on older machines. Is there a more efficient method for updating a text input field without using (keyup) to update after each keystroke? I haven't been able to find any ...

Encountering issues with package resolution in VS Code while setting up a monorepo with yarn workspaces, Vite, React, and

I recently set up a monorepo using yarn@3 workspaces. Here is the structure of my root package.json: { "name": "hello-yarn-workspaces", "packageManager": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" ...

Steps to refresh a variable when the SMS read plugin successfully completes

I'm attempting to make a post call within the success callback of my SMS read plugin code. I can successfully print _this.otpnumber in the console. Please refer to my stack trace image link getSMS(){ var _this= this; var fil ...

Mapping an HTTP object to a model

Currently facing an issue with mapping an http object to a specific model of mine, here is the scenario I am dealing with: MyObjController.ts class MyObjController { constructor ( private myObj:myObjService, private $sco ...

The updated version of Angular from 13 to 16 has implemented a new default value for primary colors

I recently upgraded my angular (+material-ui) system from version 13 to 16, and encountered an issue with the primary color of my custom theme. It seems that the value is no longer being recognized and defaults to blue. After updating the angular version, ...

Instructions for implementing personalized horizontal and vertical scrolling within Angular 9

I am currently working on an angular application where users can upload files, and I display the contents of the file on the user interface. These files may be quite long, so I would need vertical scrolling to navigate through them easily. Additionally, fo ...

The background image causes the scrollbar to vanish

As a beginner, I am in the process of creating a web page that features a consistent background image. However, I have encountered an issue where the scroll bar does not appear on a specific page called "family details" due to the background image. I atte ...

Creating a method in Angular that combines async/await functionality with Observables

After transitioning from using async/await to Observables in Angular, I am trying to refactor the following code snippet to make it work with Observables: async refreshToken() { const headers = this.authStorage.getRequestHeader(); const body = { ...

What is the best way to invoke a method within the onSubmit function in Vuejs?

I am facing an issue with a button used to log in the user via onSubmit function when a form is filled out. I also need to call another method that will retrieve additional data about the user, such as privileges. However, I have been unsuccessful in makin ...

Guidance on showcasing the current day's weekday name through TypeScript

I am perplexed about how to begin in TypeScript after successfully setting up the display using gate. ...