Skip to main content
Ben Nadel at FirstMark Tech Summit (New York, NY) with: Mike Boufford
Ben Nadel at FirstMark Tech Summit (New York, NY) with: Mike Boufford ( @mboufford )

Scrolling An Overflow-Container Back To The Top On Content Change In Angular 7.2.7

By on

Yesterday, I shared my feelings on tightly-coupled DOM (Document Object Model) access in an Angular 7.2.7 application; and, about how I'm going to stop bending over backwards in an attempt to fulfill some dogmatic separation of concerns when a payoff is never going to be realized. Along the same lines, I wanted to quickly look at another DOM-access situation that comes up often for me in my Single-Page Applications (SPA): having to scroll an overflow container back to the top when its content changes. And, just as with my previous post, I'm going to use tightly-coupled DOM-access methods to accomplish this in Angular 7.2.7.

Run this demo in my JavaScript Demos project on GitHub.

View this code in my JavaScript Demos project on GitHub.

To quickly explore this idea, let's imagine a tabbed-content area User Interface (UI). If the tab-body container, in this UI, is an overflow container, there's a good chance that the tab-body scroll-offset will have to reset when the user navigates from one tab to the next tab.

To implement this scroll-offset reset, we're going to reach into the View of our Angular Component, find the native HTML Element, and imperatively call its .scrollTo() method when the selected Tab content changes. This will tightly couple our Angular Component to the view-rendering layer; but, it will make our lives much easier.

Now, in yesterday's demo, I used the native .querySelector() method on the Host element in order to locate the HTML Element in question. So today, in an effort to mix things up a little bit, I'm going to use a ViewChild() query to inject our target element's "ElementRef" wrapper into our Angular Component. Then, when I need to reset the scroll-offset of the tab content, I can use the "nativeElement" property of the injected ViewChild query.

// Import the core angular services.
import { Component } from "@angular/core";
import { ElementRef } from "@angular/core";
import { ViewChild } from "@angular/core";

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

@Component({
	selector: "my-app",
	queries: {
		"tabsContentRef": new ViewChild( "tabsContentRef" )
	},
	styleUrls: [ "./app.component.less" ],
	template:
	`
		<div class="tabs">
			<nav class="tabs__nav">
				<a
					(click)="show( 'one' )"
					class="tabs__nav-item"
					[class.tabs__nav-item--on]="( selectedTab === 'one' )">
					Show One
				</a>
				<a
					(click)="show( 'two' )"
					class="tabs__nav-item"
					[class.tabs__nav-item--on]="( selectedTab === 'two' )">
					Show Two
				</a>
			</nav>
			<div #tabsContentRef class="tabs__content" [ngSwitch]="selectedTab">
				<div *ngSwitchCase="( 'one' )" class="tabs__tab">

					<h2>
						Tab One
					</h2>

					<p *ngFor="let i of [0,1,2,3,4,5,6,7,8,9,10]">
						This is tab one content ......
					</p>

				</div>
				<div *ngSwitchCase="( 'two' )" class="tabs__tab">

					<h2>
						Tab Two
					</h2>

					<p *ngFor="let i of [0,1,2,3,4,5,6,7,8,9,10]">
						This is tab two content ......
					</p>

				</div>
			</div>
		</div>
	`
})
export class AppComponent {

	public selectedTab: "one" | "two";
	public tabsContentRef!: ElementRef; // Using "definite assignment" assertion (query).

	// I initialize the app component.
	constructor() {

		this.selectedTab = "one";

	}

	// ---
	// PUBLIC METHODS.
	// ---

	// I show the given tab.
	public show( tab: "one" | "two" ) : void {

		this.selectedTab = tab;
		// By default - the default behavior of the browser - when we change the content
		// of an overflow-container, the overflow-container doesn't change its scroll
		// offset unless it suddenly has less content than it did before. As such, when
		// the tab-content changes, we have to explicitly scroll the overflow-container
		// back to the top.
		this.scrollTabContentToTop();

	}

	// ---
	// PRIVATE METHODS.
	// ---

	// I scroll the tab-content overflow-container back to the top.
	private scrollTabContentToTop() : void {

		this.tabsContentRef.nativeElement.scrollTo( 0, 0 );

	}

}

As you can see, when a user clicks on one of the tabs, we update the Angular Component's view-model in order to change the tab-content. And then, we immediately call the .scrollTabContentToTop() method. This method reaches into the DOM and invokes the native DOM-method, .scrollTo(), which will scroll our overflow-container back to the top.

Now, if we run this Angular application in the browser and switch from tab to tab, we can see that the tab-content container is scrolled back to the top on each tab-change:

Embracing tightly-coupled DOM-access in order to call scollTo() and reset the scroll-offset of an overflow container in Angular 7.2.7.

This kind of approach used to feel "dirty" to me because I didn't want my Angular Component classes to know about the rendered DOM. But, in some cases, like this, reaching directly into the DOM in order to manipulate it makes the code easier to understand and maintain. And, again, if we do ever need to create better separation between our Classes and our Templates, we can always refactor the code to use more indirection.

Want to use code from this post? Check out the license.

Reader Comments

15,663 Comments

@Bogdan,

I don't know much about Blogger - at least not from a technical standpoint. All I can say is that the .scrollTo( 0, 0 ) method is part of the normal browser DOM API. As such, you should be able to use that with or with Angular. You might just have to bind to a click handler and then call something like, window.scrollTo( 0, 0 );.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel