Quirks in the layout of native scrollable CSS windows viewed through Angular's ViewChild

In my Angular 19 application, I have a component with a vertically scrollable window that hides content outside the visible area. The window extends beyond the top and bottom edges, and the content can be centered by pressing a button. There is no horizontal scrolling.

The initial centering of the content is achieved using an effect in the constructor:

element = viewChild.required<ElementRef>('myWindow');

#scrollTo = computed(() => {
  const nativeElement = this.element().nativeElement;
  const scrollHeight: number = nativeElement.scrollHeight;
  const offsetHeight: number = nativeElement.offsetHeight;

  return 4 + (scrollHeight - offsetHeight) / 2;
});

constructor() {
  effect(() => 
    this.element().nativeElement.scrollTo(0, this.#scrollTo())
  );
}

Most of the time, the scrollHeight is 700px and the offsetHeight is 300px, and everything works correctly. However, about one out of ten to fifteen refreshes in Chrome, both heights are the same at 38px, causing the centering to fail.

Hard-coding the scroll-to value does not solve the problem, and it's not a feasible solution either.

I suspect this issue may be due to a race condition between Chrome's layout calculation and the scrollTo signal calculation. Any ideas on how to fix this calculation or behavior without introducing a delay in the component constructor?

Here's a StackBlitz demonstration of the problem; you can modify the window component's constructor to test different scenarios.

UPDATES

I've reported an Angular bug as it seems to be a framework issue. A workaround is required until an Angular fix is provided.


As mentioned by Matthieu Riegler, the correct effect for DOM interactions is renderAfterEffect. Unfortunately, this approach currently fails without a timeout or by disabling Hot Module Replacement (HMR):

constructor() {
  renderAfterEffect(() => 
    setTimeout(() =>
      this.element().nativeElement.scrollTo(0, this.#scrollTo()),
      200
    )
  );
}

As pointed out by Naren Murali in the second part of their answer, adding a timeout delay can circumvent the problem:

constructor() {
  effect(() => 
    setTimeout(() =>
      this.element().nativeElement.scrollTo(0, this.#scrollTo()),
      200
    )
  );
}

However, according to Matthieu Riegler, the correct workaround should involve using renderAfterEffect.

Answer №1

Instead of using the effect function, it is recommended to utilize the afterRenderEffect() method:

constructor() {
  afterRenderEffect(() => 
    this.element().nativeElement.scrollTo(0, this.#scrollTo())
  );
}

The effect function runs before synchronization occurs, whereas the afterRenderEffect() function runs after rendering is complete. This API is specifically beneficial for scenarios where DOM manipulation is required.


Note: According to responses in the Angular issue, the problem is related to HMR affecting the timing of style loading and callback execution, resulting in incorrect readings of offsetHeight.

A temporary solution is to disable HMR using the --no-hmr flag.

Answer №2

It appears that the issue you are experiencing may be related to how the content is being loaded via an API call. Instead of using ng-init from angularjs, consider utilizing a directive with similar functionality.

import { Directive, EventEmitter, Output } from '@angular/core';

@Directive({
  selector: '[ngInit]',
  exportAs: 'ngInit'
})
export class NgInitDirective {
  @Output()
  ngInit: EventEmitter<any> = new EventEmitter();

  ngOnInit() {
    this.ngInit.emit();
  }
} 

By using this directive, we can trigger events on HTML elements and perform computations accordingly.

<div (ngInit)="scrollFunction()">... content inside </div>

One potential issue may be the use of computed values for scrolling dimensions. Consider calculating these values using functions instead.


Consider utilizing the ngAfterViewInit hook, which runs after the view (HTML) has been initialized.

An effect is not necessary in this case; a direct function call should suffice.

If issues persist, try wrapping the code in setTimeout and executing it.

ngAfterViewInit() {
    this.element().nativeElement.scrollTo(0, this.#scrollTo());
}

To ensure proper rendering completion, wrap your code in a setTimeout function.

constructor() {
    effect(() => {
        setTimeout(() => {
            this.element().nativeElement.scrollTo(0, this.#scrollTo());
        });
    }); 
}

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

Create a new `div` component for a child element when the button is clicked in ReactJS

I am attempting to incorporate a child div component (div-col inside of div-row) when the button is clicked. In this case, I am changing the card layout from grid to list view using Bootstrap classes. If the current view is set to grid view: <div class ...

Angular 4 - Consistently fetching sanitized image URLs

I have come across an issue with an image in my code: <img [attr.src]="sanitizer.bypassSecurityTrustUrl(imgSource)"> The problem is that even though the imgSource remains constant after being retrieved once, the image keeps making repeated reques ...

Encountered an error while attempting to use the 'setAttribute' method on the 'Element' object: ']' is not a recognized attribute name. This issue arose within an Angular 4 project

I encountered the following issue: Failed to execute 'setAttribute' on 'Element': ']' is not a valid attribute name. I defined a model as follows: export interface ModalComponentModel { username: string; password: s ...

Struggling with textpath SVG elements in jQuery

Currently, I am implementing SVG in my project and aiming to apply the toggleClass function using jQuery on the textpath elements upon clicking. My initial plan was: $("text#names > textpath").click(function() { $(this).toggleClass("newClass"); }) ...

Tips on styling HTML using a query value in string format

When using ASP.NET razor and SQL Server 2008 R2, I have a local variable declared in the following manner: var tsmMtd = db.QueryValue(" SELECT b.Name, SUM(subtotal) totalSUM FROM DR_TRANS a INNER JOIN Staff b ON a.Salesno = b.Staffno WHER ...

Challenges with Mobile-Webkit Scrolling and Swiping on Horizontal One-Page Websites

Take a look at this demo I've been working on: http://jsfiddle.net/3N8wY/9/ Problem #1 When accessing the link from a stock Android browser or an iOS device, the website doesn't scroll properly. It seems to glitch and pulse without moving anywh ...

What is the process of parsing JSON in an HTML file?

Can someone help me figure out how to parse params in HTML and get clean parameters from the following code? <a [routerLink]="[menu.url||'/']" [queryParams]="menu.refParameter3?JSON.parse(menu.refParameter3):{}" ...

Struggling to align the div horizontally using text-align?

I need to make the div align horizontally. .d1 { margin-left: 1rem; margin-right: 1rem; font-size: 0; text-align: justify; } .d1::after { content: ''; font-size: 0; line-height: 0; height: 0; width: 100%; di ...

`To update the index from a different page, follow these steps`

My webpage features a straightforward grid displaying information from the database on index.php. Users can click on a link to edit a record, which redirects them to edit.php. After editing, I am seeking a method to automatically refresh index.php with the ...

I'm having trouble getting my button to work with addEventListener while using Ejs and Express. What could

Creating a Twitter-like platform where I can post tweets and have them display on the same page has been quite challenging. I've set up each post to have an Edit button initially hidden with 'display:none'. However, when I try to click on th ...

Getting Started with Icons in NativeScript and Angular: A Step-by-Step Guide

I'm having trouble incorporating icons into my nativescript + angular app for both iOS and Android. I've experimented with various methods of setting up icons, including following this tutorial, using this solution, as well as attempting to utili ...

Stop the CSS animation on the circular menu to toggle it open and closed

I have a circular menu with the added code "animation: pulse infinite". The menu can be opened and dragged around. Now, I want to add a pause animation so that it stops for a while when the menu is opened and closed. I tried adding "animation-play-state" ...

Unsure about the commit hash referenced in the tsd.json file

Explore the history of angular-ui-router d.ts file using the commit hash Within my tsd.json file, I have the following details: { "version": "v4", "repo": "borisyankov/DefinitelyTyped", "ref": "master", "path": "typings", "bundle": "typings/tsd ...

Implementing flexible number of generic arguments in Typescript for React components

When working with ReactJS and TypeScript, I found a clever way to conditionally render components based on the truthiness of a given property. Here is an example implementation: // Defining a type helper for Maybe export type Maybe<T> = T | undefined ...

The list is shifting unexpectedly as the font size transitions

Hey guys, I've created a cool interactive design with six boxes that increase in font size when hovered over. However, this causes the boxes to move around, as you can see in my example. Any suggestions on how to fix this issue? Another thing I notic ...

Can you provide the syntax for a generic type parameter placed in front of a function type declaration?

While reviewing a project code recently, I came across some declarations in TypeScript that were unfamiliar to me: export interface SomeInterface<T> { <R>(paths: string[]): Observable<R>; <R>(Fn: (state: T) => R): Observable ...

Switching page upon submission while utilizing iFrames

I am facing a challenge on my website with an iFrame element that contains a form. After users answer some questions and submit the form, I want the page to automatically redirect to a thank you page. Unfortunately, I'm having trouble making this work ...

When accessing the route "/[locale]", make sure to await the `params` object before utilizing its properties like `params.locale`

Currently, I am developing a Next.js 15 application utilizing the new App Router (app directory) with dynamic route localization. Within my project, I have implemented a [locale] directory to manage multiple language routes and utilize the params object to ...

Is there a way to implement imports based on a specific condition?

I'm just starting to learn Angular and TypeScript. My goal is to redirect multiple hostnames to a single Angular 6 project because most aspects are the same, with only language and URLs varying. For example, here's what I have implemented in ap ...

Using FEWER loops to create column classes on Twitter - What is their functionality?

Bootstrap utilizes various LESS mixins to create its column classes, along with other classes; .make-grid-columns() { // Common styles for all sizes of grid columns, widths 1-12 .col(@index) when (@index = 1) { // initial @item: ~".col-xs-@{index} ...