Scroll based on URL fragment (Angular + NGRX + ElementRef)

Viewed 139

I have a main page with couple of sections. Each section can be reached by typing its fragment name on the url (Ex. www.website.com/#introduction - will open the homepage and scroll to the introduction section automatically). In order to do that, I need to fetch the section’s container element and run .scrollIntoView() function on it.

What I have done:

  1. List item I have created a store (ngrx) which stores the ElementRef(s) of the sections.
  2. In each section component (located in HomeComponent - parent of the sections), I put a @ViewChild() pointing on the element. @ViewChild(‘container’, { static: true }) private container: ElementRef;
  3. Then on ngAfterViewInit() of this section component, I put Store.dispatch() which stores my ElementRef on the store.
this.store.dispatch(new HomeActions.SetElementRef({
  linkName: COMP_LINK_NAME,
  elementRef: this.container
}));

Then, in HomeComponent, I listen to the sectionsRef store this.store.select('sectionRefs'). Filtered it if its undefined (as it is the default of the store = default statement is empty).

this.sectionRefs$ = this.store.select('sectionRefs').pipe(
    map((data: { sectionRefs: SectionReference[] }) => data.sectionRefs), // transform to a more convenient object
    tap((sectionRefs: SectionReference[]) => this.sectionRefs = sectionRefs), // Updates the sectionRefs array regardless the completion of it (filter below)
    filter((sectionRefs: SectionReference[]) => { // No emission until sectionRefs Element Refs are completed
      return sectionRefs.find((sectionRef: SectionReference) => {
        return (
          sectionRef.element === undefined ||
          sectionRef.element === null ||
          sectionRef.element.nativeElement.offsetHeight === 0 && sectionRef.element.nativeElement.offsetWidth === 0
        );
      }) === undefined // Means that no incompleted sectionRef was found
    }),
    distinctUntilChanged(
      (prev: SectionReference[], next: SectionReference[]) => JSON.stringify(prev) === JSON.stringify(next)
    ),
    first()
);
  1. HomeCompoent: And eventually I listened to the ActivatedRoute.fragment, to check which ElementRef is relevant for the scrolling.
  2. HomeComponent: Activate .scrollIntoView() on the relevant ElementRef.

The issue is that I do it when the page is loaded so the ElementRef.nativeElement.scrollIntoView() is scrolling to incorrect place. I checked to see the offsetHeight retrieved (ElementRef.nativeElement.offsetHeight) and it was also incorrect. So In each section Component I wrapped the Store.dispatch() action with setTimeout()

setTimeout(() => {
  this.store.dispatch(new HomeActions.SetElementRef({
  linkName: COMP_LINK_NAME,
  elementRef: this.container
  }));
}, 150);

And it works! But it is looking like a very bad practice to do this work around.

Is there any other solution?

0 Answers
Related