Smooth scrolling is something all UXers want.

Optimally I would use the Angular ViewportScroller, but it doesn’t support smooth scrolling. Instead, we create our own scroll service.

Fortunately it has become really easy to implement, since smooth scrolling has native support in all major browsers.

Even though, there’s an almost 100% supported Element.scrollTo, the safe bet is to go with Window.scrollTo.

Going with Window.scrollTo, for the wider support, also means we must convert from the viewport position (think position in the browser window) of the element obtained through getBoundingClientRect, to a document position. However, that’s pretty straightforward:

const elTopViewportPosition = targetEl.getBoundingClientRect().top;
const elTopAbsolutePosition = elTopViewportPosition + this._document.documentElement.scrollTop;

And when we have the absolute position, the scrollTo with a smooth argument can be used:

this._window?.scrollTo({
  top: elTopAbsolutePosition + offset,
  behavior: 'smooth',
});

At last, for assistive technology / tabbing to work the element jumped to needs to be focused.

targetEl.focus();

The complete scroll service:

import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';

@Injectable({providedIn: 'root'})
export class ScrollService {

  private _window: Window | null;

  constructor(@Inject(DOCUMENT) private _document: Document) {
    this._window = _document.defaultView;
  }

  scrollTo(id: string, offset = 0) {
    const targetEl: HTMLElement | null = this._document.querySelector('#' + id);
    if (!targetEl || !this._window) {
      return;
    }

    const elTopViewportPosition = targetEl.getBoundingClientRect().top;
    const elTopAbsolutePosition = elTopViewportPosition + this._document.documentElement.scrollTop;
    
    this._window.scrollTo({
      top: elTopAbsolutePosition + offset,
      behavior: 'smooth',
    });
    
    targetEl.focus();
  }
}

Demo