Reputation: 669
I have a dynamic list that gets its data asynchronously, and would like to scroll an specific element into view when the list is loaded.
The list is build similar to this:
<div class="items" *ngFor="let item of functionThatGetsItems(); let i = index" [id]="'MyList' + i">
<div class="title">
{{ item.title }}
</div>
<div class="content">
{{ item.content }}
</div>
</div>
As you can see, the items have an ID assigned that is simple a base string and their number. Let's say I want to trigger a scroll to MyList31
. Once the list is loaded and rendered, how do I fetch the element with such ID and scroll to it?
I've searched around and found the ways you should not do it, and how to do it using ViewRefs, but these don't seem to work on dynamic elements, or do they? How would I do this?
Upvotes: 17
Views: 27117
Reputation: 133
You can use the following approach. First, modify the element that matches the specified ID to include a template reference variable for the target element. Simultaneously, create a duplicate of the element without a template reference variable for those elements that do not match the specified ID.
<ng-container *ngFor="let item of functionThatGetsItems(); let i = index">
<div #target *ngIf="i === id"></div>
<div *ngIf="i !== id"></div>
</ng-container>
In your component, use ViewChild
to get a reference to the target element and then call scrollIntoView()
to scroll it into view.
@ViewChild('target') target: ElementRef;
...
this.target.nativeElement.scrollIntoView(...);
Upvotes: 0
Reputation: 151
You want the id to be on the actual item that the ng-for creates, not the ng-for itself. That would eliminate the need for any extra logic when passing data to the list from the component.
// inside ngAfterViewInit() to make sure the list items render or inside ngAfterViewChecked() if you are anticipating live data using @Inputs
const itemToScrollTo = document.getElementById('item-' + id);
// null check to ensure that the element actually exists
if (itemToScrollTo) {
itemToScrollTo.scrollIntoView(true);
}
<div class="list" *ngFor="let item of functionThatGetsItems(); let i = index">
<div class="list-item" id="item-{{i}}">
<div class="title">
{{ item.title }}
</div>
<div class="content">
{{ item.content }}
</div>
</div>
</div>
Upvotes: 14
Reputation: 25525
Use a template reference and call the native scrollIntoView()
method. In your html:
<h2 #scrollToMe>Hi there</h2>
In your component:
@ViewChild('scrollToMe') scrollToMe: ElementRef;
...
this.scrollToMe.nativeElement.scrollIntoView({ behavior: 'smooth' })
The behavior: 'smooth'
animates the transition.
Upvotes: 1
Reputation: 669
I managed to solve this using property binding.
First, when I receive the parameter that defines which item to scroll into, I save it as a component property.
this.toScrollInto = Object.keys(params)[0];
Then, inside of the ngFor that builds it, I bind into it and make use of short-circuiting to call a function if there is a match.
<div class="items" *ngFor="let item of functionThatGetsItems(); let i = index"
#itemRef
[class.scrolled]="i == this.toScrollInto && scrollIntoView(itemRef)">
<div class="title">
{{ item.title }}
</div>
<div class="content">
{{ item.content }}
</div>
</div>
The function scrollIntoView(Element)
then handles the scrolling using the provided Angular reference.
Upvotes: 1
Reputation: 3845
This is not strictly angular, but you could do document.querySelector('#MyList31').scrollIntoView()
.
Reference https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
For doing this strictly angular, this article may help you http://www.benlesh.com/2013/02/angular-js-scrolling-to-element-by-id.html
Upvotes: 7