In my Angular App (currently Angular 11) I always used a back-to-top-button which appears when the user scrolls. The button scrolls the window back to top when it gets clicked and then disappears. Classic behaviour.
But now I changed my layout and replaced bootstrap navbars 'n stuff by Angular Material Tabs.
My BodyComponent now looks somehow like this:
<div id="body.component.container" style="margin-top: 62px;">
<mat-tab-group [(selectedIndex)]="selectedMatTabIndex">
<mat-tab>
<ng-template matTabLabel>
<span style="font-family: 'Arial Narrow', monospace; font-size: 16px; font-weight: bold;">Tab1</span>
</ng-template>
<app-content-component-001></app-content-component-001>
</mat-tab>
<mat-tab>
<ng-template matTabLabel>
<span style="font-family: 'Arial Narrow', monospace; font-size: 16px; font-weight: bold;">Tab2</span>
</ng-template>
<app-content-component-002></app-content-component-002>
</mat-tab>
</mat-tab-group>
</div>
<app-back-to-top
[acceleration]="1000"
[animate]="true"
[scrollDistance]="50"
[speed]="5000">
</app-back-to-top>
The problem I am facing is, that there is no common scrolling event catchable any more. Usually, As you all surely know, inside a back-to-top-button component one listens to the HostEvent window:scroll
but this does not work inside MatTabs.
@HostListener('window:scroll', [])
onWindowScroll() {
if (this.isBrowser()) {
this.animationState = this.getCurrentScrollTop() > this.scrollDistance / 2 ? 'in' : 'out';
}
}
And it does not matter where I put this back-to-top-button-component at. I tried putting it directly into the body component (that's how it worked out for years), into the container-div, into the MatTabGroup and into each MatTab. The window:scroll
-event does not show up.
During my (Re)Searching through the internet I found some faint hints that I have to use some directives of CDK's but no example how.
So I've got to questions.
I found the solution myself. It is actually the way I tried to go before posting here. I have to use the cdkScrollable
Directive.
And now I know how. You have to put a div around the component that is displayed inside each MatTab. And then you have to attach the cdkScrollable
Directive to these divs. Then you can catch the scroll-event with a ScrollDispatcher inside the TS-code.
Finally it looks like this:
The HTML of my BodyComponent
<div id="body.component.container" style="margin-top: 62px;">
<mat-tab-group [(selectedIndex)]="selectedMatTabIndex">
<mat-tab>
<ng-template matTabLabel>
<span style="font-family: 'Arial Narrow', monospace; font-size: 16px; font-weight: bold;">Tab1</span>
</ng-template>
<div cdkScrollable>
<app-content-component-001></app-content-component-001>
</div>
</mat-tab>
<mat-tab>
<ng-template matTabLabel>
<span style="font-family: 'Arial Narrow', monospace; font-size: 16px; font-weight: bold;">Tab2</span>
</ng-template>
<div cdkScrollable>
<app-content-component-002></app-content-component-002>
</div>
</mat-tab>
</mat-tab-group>
</div>
<app-back-to-top
[scrollingNativeElement]="scrollingNativeElement">
</app-back-to-top>
The TS of my BodyComponent (only the important lines of code)
import { CdkScrollable, ScrollDispatcher } from '@angular/cdk/overlay';
scrollingNativeElement: HTMLElement;
constructor(public scrollDispatcher: ScrollDispatcher){}
ngOnInit(): void {
this.scrollDispatcher.scrolled().subscribe((data: CdkScrollable) => {
this.scrollingNativeElement = data.getElementRef().nativeElement;
});
}
The TS of my BackToTopButtonComponent
import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
@Component({
selector: 'app-back-to-top',
templateUrl: './back-to-top.component.html',
styleUrls: ['./back-to-top.component.css'],
animations: [
trigger('appearInOut', [
state('in', style({
'display': 'block',
'opacity': '1'
})),
state('out', style({
'display': 'none',
'opacity': '0'
})),
transition('in => out', animate('400ms ease-in-out')),
transition('out => in', animate('400ms ease-in-out'))
]),
]
})
export class BackToTopComponent implements OnInit, OnDestroy, OnChanges {
animationState = 'out';
@Input() scrollingNativeElement: HTMLElement;
ngOnInit(): void
{
}
ngOnDestroy(): void
{
}
ngOnChanges(changes: SimpleChanges): void
{
if (changes['scrollingNativeElement'].currentValue)
{
this.animationState = (this.scrollingNativeElement.scrollTop > 0) ? 'in' : 'out';
}
}
scrollToTop(): void
{
this.scrollingNativeElement.scrollTo(0, 0);
}
}
The HTML of my BackToTopButtonComponent
<button mat-fab type="button"
id="BT1000"
aria-label="Back to top of the page"
class="back-to-top-button"
[@appearInOut]="animationState"
(click)="scrollToTop()"
matTooltip="scroll to top">
<mat-icon class="back-to-top-mat-icon" svgIcon="YOUR ICON GOES HERE"></mat-icon>
</button>
The CSS of my BackToTopButtonComponent
.back-to-top-button {
position: fixed;
right: 40px;
bottom: 40px;
border: 0;
outline: none;
color: black;
background: #f2f2f2;
text-decoration: none;
cursor: pointer;
z-index: 9999;
}
.back-to-top-mat-icon {
transform: scale(1.5);
}