Endy Tjahjono
Endy Tjahjono

Reputation: 24500

How to call scrollIntoView on an element in angular 2+

I want to call scrollIntoView on a HTML element in a component.

In angular.js 1 I can do something like this in the controller:

var element = $window.document.getElementById(id);
element.scrollIntoView({ behavior: "smooth", block: "start" });

How can I do the same thing in angular 2+ ?

Upvotes: 38

Views: 98515

Answers (10)

Surya R Praveen
Surya R Praveen

Reputation: 3745

Anchor tag doesn't work due to rendering on real time for scroll

  • scrollIntoView() - Not working
  • scrollIntoViewIfNeeded() - Not working

Use native element WINDOW.SCROLL and SET TIME OUT

Find the ID present within DOM, then map it to array and grab the URL's HASH, match with the ID. If true then use window scroll event using offset.

Hope it helps!!!

Below is the example of using nativeElement

Article-component.ts

import { DOCUMENT, isPlatformBrowser } from '@angular/common';
import {  Component,  ElementRef,  Inject,  OnDestroy,  OnInit,  PLATFORM_ID,  QueryList,  ViewChildren } from '@angular/core';

@Component({
  selector: 'app-article-details',
  templateUrl: './article-details.component.html',
  styleUrls: ['./article-details.component.scss'],
})
export class ArticleDetailsComponent implements OnInit, OnDestroy {
  @ViewChildren('section') articleSections!: QueryList<ElementRef>;
  data$!: Observable<Insight | null>;

  private isBrowser: boolean = false;
  siteURL: any;
  hashID : any;

  constructor(
    @Inject(PLATFORM_ID) platformId: Object,
    @Inject(DOCUMENT) private dom: Document,
  ) {
    this.isBrowser = isPlatformBrowser(platformId);
  }

  ngOnInit(): void {
    this.siteURL = new URL(this.dom.URL);
    this.isAnchor();
  }

//Scrolling to Anchor ID
  isAnchor() {
    this.hashID = this.siteURL.hash;
    this.hashID = this.hashID.replace('#', '');

    setTimeout(() => {
      const foundID = Array.from(this.articleSections).find(
        (el) => el.nativeElement.id === this.hashID
      );
      if (!foundID) return;
      if (this.isBrowser) {
        window.scroll({
          top: foundID?.nativeElement.offsetTop - 200,
          left: 0,
          behavior: 'smooth',
        });
      }
    }, 400);
  }
}

Article-component.html

  <ng-container *ngFor="let slice of vm.data.slices.items">    
         <ng-container [ngSwitch]="slice.__typename">
            <section #section [attr.id]="slice.quickLink?.slug">
              <app-slice-wysiwyg
                *ngSwitchCase="articleDetailsSlice.Wysiwyg"
                [slice]="$any(slice)"
              ></app-slice-wysiwyg>
            </section>
          </ng-container>
   </ng-container>

Upvotes: 0

Bullsized
Bullsized

Reputation: 605

Here's my take (also, this works on Angular 15). Add an empty section to your template:

<section #resultsStart></section>

Refer to it in the TS file:

@ViewChild('resultsStart', { read: ElementRef }) resultsStart: ElementRef;

And after that when some event happens (for example, page change):

    this.resultsStart.nativeElement.scrollIntoView({
      behavior: 'smooth',
      block: 'end',
    });

For more information on the options, you can check this article.

Upvotes: 1

Vương Hữu Thiện
Vương Hữu Thiện

Reputation: 1708

There are 3 simple ways to use scrollIntoView

Link to Stackblitz

The first way

In app.component.html file:

<button (click)="scrollPoint1(point_1)">
  Click to scroll - Point 1
</button>

<div>
  <h3 #point_1>Point 1</h3>
</div>

In app.component.ts file:

scrollPoint1(el: HTMLElement) {
  // el.scrollIntoView({behavior: "smooth"});
  el.scrollIntoView();
}

Second way

In app.component.html file:

<button click)="scrollPoint2()">
  Click to scroll - Point 2
</button>

<div>
  <h3 id="point_2">Point 2</h3>
</div>

In app.component.ts file:

scrollPoint2() {
  // document.getElementById("point_2").scrollIntoView({behavior: "smooth"});
  document.getElementById('point_2').scrollIntoView();
}

Third way

In app.component.html file:

<button (click)="scrollPoint3()">
  Click to scroll - Point 3
</button>

<div>
  <h3 #point_3>Point 3</h3>
</div>

In app.component.ts file:

@ViewChild('point_3') private my_point_3: ElementRef;
scrollPoint3() {
  // this.my_point_3.nativeElement.scrollIntoView({behavior: "smooth"});
  this.my_point_3.nativeElement.scrollIntoView();
}

Upvotes: 7

Crikey Mikey
Crikey Mikey

Reputation: 125

I could only get it working by wrapping in requestAnimationFrame or setTimeout

@ViewChild('someElement') someElementRef: ElementRef;

...

requestAnimationFrame(() => {
  this.someElementRef.nativeElement.scrollIntoView({ behavior: 'smooth' });
});
<div #someElement>test</div>

Upvotes: 1

gil
gil

Reputation: 2552

it's already been answered a while ago but the cleanest way to achieve it is doing that on directive which also provides reusability.

here's an example for directive which will scroll an element into view, triggered by mouse click:

import {Directive, ElementRef, HostListener} from '@angular/core';

@Directive({
    selector: '[ScrollIntoViewOnClick]'
})
export class ScrollIntoViewOnClickDirective {

    constructor(private _elementRef: ElementRef) {
    }

    @HostListener('click', ['$event.target'])
    scrollIntoView() {
        setTimeout(() => {
            this._elementRef.nativeElement.scrollIntoView({behavior: 'smooth'});
        }, 200)
    }
}

then all you got to do is add that directive on the relevant element to trigger that functionality when it is clicked.

hope it helps anyone.

Upvotes: 5

Marcus Gallegos
Marcus Gallegos

Reputation: 1592

I'm using Capacitor to wrap an Angular app. scrollIntoView wasn't working but wrapping it in a setTimeout() function to make it a synchronous call worked. I don't know why. I've had a similar issue when trying to open modals/dialogs in the constructor. .

Upvotes: 2

Uliana Pavelko
Uliana Pavelko

Reputation: 2952

You can achieve the same animated scroll to the element in Angular 2+, simply pass the element on click, like so:

<button mat-fab (click)="scroll(target)">
    <i class="material-icons">
      arrow_drop_down
    </i>
 </button>
<div #target></div>

public scroll(element: any) {
    element.scrollIntoView({ behavior: 'smooth' });
  }

Upvotes: 13

Kenneth Werf
Kenneth Werf

Reputation: 41

We had some issues with our hybrid where none of this worked. If you just want a simple solution you could also opt for adding a timeout to break out of Angular.

E.g.

<a (click)="onClick('foo')">

onClick(id: string): void {
    const el: HTMLElement|null = document.getElementById(id);
    if (el) {
      setTimeout(() =>
        el.scrollIntoView({behavior: 'smooth', block: 'start', inline: 'nearest'}), 0);
    }
  }

Upvotes: 4

Alberto L. Bonfiglio
Alberto L. Bonfiglio

Reputation: 1835

You could intercept the NavigationEnd event

    private scrollToSectionHook() {
    this.router.events.subscribe(event => {
        if (event instanceof NavigationEnd) {
            const tree = this.router.parseUrl(this.router.url);
            if (tree.fragment) {
                const element = document.querySelector('#' + tree.fragment);
                if (element) {
                    setTimeout(() => {
                        element.scrollIntoView({behavior: 'smooth', block: 'start', inline: 'nearest'});
                    }, 500 );
                }
            }
         }
    });
}

Upvotes: 19

Endy Tjahjono
Endy Tjahjono

Reputation: 24500

First add a template reference variable in the element (the #myElem):

<p #myElem>Scroll to here!</p>

Then create a property in the component with attribute ViewChild, and call .nativeElement.scrollIntoView on it:

export class MyComponent {
  @ViewChild("myElem") MyProp: ElementRef;

  ngOnInit() {
    this.MyProp.nativeElement.scrollIntoView({ behavior: "smooth", block: "start" });
  }
}

Upvotes: 73

Related Questions