Embedded in a large Angular 19 application, I have a component with a vertically scroll-able window where the content outside the window is hidden, i.e., the window content extends beyond the top and bottom of the fixed window and the content can be centered with a button press. There is no horizontal scrolling.
Initial centering of the content is done with an effect in the constructor:
element = viewChild.required<ElementRef>('my-container');
#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 scrollHeight
is 700px and offsetHeight
is 300px and things work properly; however, about one in ten or fifteen refreshes (in Chrome) the scrollHeight
and offsetHeight
are the same, in this case 38px which cause the centering to fail.
As you might guess, hard-coding the scroll-to value does not fix the problem (and it would not be a viable solution, either).
I’m guessing this is race-condition between the Chrome’s layout calculation and the scrollTo
signal calculation? Any ideas how to fix this calculation and/or behavior, without introducing a delay in the component constructor?
As Naren Murali noted in the second part of his answer, a delay will work-around the problem; with the following I didn’t see a failed centering in fifty+ refreshes:
constructor() {
effect(() =>
setTimeout(() =>
this.element().nativeElement.scrollTo(0, this.#scrollTo()),
200
)
);
}
Angular bug report:
2
Answers
You should use the
ngAfterViewInit
hook which triggers after the view(HTML) is initialized.We do not need an
effect
here, just a direct call will suffice.If it does not work, try wrapping in a setTimeout and execute.
Wrap your code in a
setTimeout
so that the rendering might complete.This
effect
should actually be anafterRenderEffect()
:effect
is known to run before the sync process, whileafterRenderEffect()
runs after the app has been rendered. This API is specifically recommended for this kind of cases where you want to read & alter the DOM.