Reputation: 24500
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
Reputation: 3745
Anchor tag doesn't work due to rendering on real time for scroll
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
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
Reputation: 1708
There are 3 simple ways to use scrollIntoView
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
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
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
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
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
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
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
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