Skip to content

Commit

Permalink
fix: use rxjs fromEvent instead of renderer.listener to get scroll ev…
Browse files Browse the repository at this point in the history
…ents (#16896)
  • Loading branch information
can-oezkan authored Dec 9, 2024
1 parent 65dfb15 commit 952aab3
Showing 1 changed file with 64 additions and 97 deletions.
161 changes: 64 additions & 97 deletions apps/showcase/components/doc/app.docsection-nav.component.ts
Original file line number Diff line number Diff line change
@@ -1,124 +1,100 @@
import { Doc } from '@/domain/doc';
import { DOCUMENT, isPlatformBrowser, Location } from '@angular/common';
import { ChangeDetectorRef, Component, ElementRef, Inject, Input, NgZone, OnDestroy, OnInit, PLATFORM_ID, Renderer2, ViewChild } from '@angular/core';
import { Component, DestroyRef, ElementRef, inject, input, OnInit, PLATFORM_ID, signal, ViewChild } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { DomHandler } from 'primeng/dom';
import { ObjectUtils } from 'primeng/utils';
import { Subscription } from 'rxjs';
import { fromEvent } from 'rxjs';

@Component({
selector: 'app-docsection-nav',
template: ` <ul #nav *ngIf="docs && docs.length" class="doc-section-nav" [ngClass]="{ hidden: visible }">
<li *ngFor="let doc of docs; let i = index" class="navbar-item" [ngClass]="{ 'active-navbar-item': activeId === doc.id }">
<ng-container *ngIf="!doc.isInterface">
<div class="navbar-item-content">
<button class="px-link" (click)="onButtonClick($event, doc)">{{ doc.label }}</button>
</div>
<ng-container>
<ul *ngIf="doc.children">
<li *ngFor="let child of doc.children; let isFirst = first" class="navbar-item" [ngClass]="{ 'active-navbar-item': activeId === child.id }">
<div class="navbar-item-content">
<button class="px-link" (click)="onButtonClick($event, child)">
{{ child.label }}
</button>
</div>
</li>
</ul>
</ng-container>
</ng-container>
</li>
</ul>`
template: `
<ul #nav class="doc-section-nav">
@for (doc of docs(); track doc.label) {
@if (!doc.isInterface) {
<li class="navbar-item" [ngClass]="{ 'active-navbar-item': activeId() === doc.id }">
<div class="navbar-item-content">
<button (click)="onButtonClick(doc)">{{ doc.label }}</button>
</div>
@if (doc.children) {
<ul>
@for (child of doc.children; track child.label) {
<li class="navbar-item" [ngClass]="{ 'active-navbar-item': activeId() === child.id }">
<div class="navbar-item-content">
<button (click)="onButtonClick(child)">
{{ child.label }}
</button>
</div>
</li>
}
</ul>
}
</li>
}
}
</ul>
`
})
export class AppDocSectionNavComponent implements OnInit, OnDestroy {
@Input() docs!: Doc[];

subscription!: Subscription;

scrollListener!: any;
export class AppDocSectionNavComponent implements OnInit {
docs = input.required<Doc[]>();

_activeId: any;

get activeId() {
return this._activeId;
}

set activeId(val: string) {
if (val !== this._activeId) {
this._activeId = val;
this.cd.markForCheck();
}
}
activeId = signal<string | null>(null);

isScrollBlocked: boolean = false;

topbarHeight: number = 0;

scrollEndTimer!: any;

@ViewChild('nav') nav: ElementRef;
private readonly document = inject(DOCUMENT);
private readonly platformId = inject(PLATFORM_ID);
private readonly location = inject(Location);
private readonly destroyRef = inject(DestroyRef);

constructor(
@Inject(DOCUMENT) private document: Document,
@Inject(PLATFORM_ID) private platformId: any,
private location: Location,
private zone: NgZone,
private renderer: Renderer2,
private cd: ChangeDetectorRef
) {}
@ViewChild('nav') nav: ElementRef;

ngOnInit(): void {
ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
const hash = window.location.hash.substring(1);
const hasHash = ObjectUtils.isNotEmpty(hash);
const id = hasHash ? hash : ((this.docs && this.docs[0]) || {}).id;

this.activeId = id;
hasHash &&
setTimeout(() => {
this.scrollToLabelById(id);
}, 250);

this.zone.runOutsideAngular(() => {
this.scrollListener = this.renderer.listen(this.document, 'scroll', (event: any) => {
this.onScroll();
});
});
this.scrollCurrentUrl();

fromEvent(this.document, 'scroll')
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => this.onScroll());
}
}

scrollCurrentUrl() {
const hash = window.location.hash.substring(1);
const hasHash = ObjectUtils.isNotEmpty(hash);
const id = hasHash ? hash : (this.docs[0] || {}).id;
const id = hasHash ? hash : (this.docs()[0] || {}).id;

this.activeId = id;
this.activeId.set(id);
hasHash &&
setTimeout(() => {
this.scrollToLabelById(id);
}, 1);
}, 250);
}

getLabels() {
return [...Array.from(this.document.querySelectorAll(':is(h1,h2,h3).doc-section-label'))].filter((el: any) => DomHandler.isVisible(el));
}

onScroll() {
if (isPlatformBrowser(this.platformId) && this.nav) {
if (isPlatformBrowser(this.platformId)) {
if (!this.isScrollBlocked) {
this.zone.run(() => {
if (typeof document !== 'undefined') {
const labels = this.getLabels();
const windowScrollTop = DomHandler.getWindowScrollTop();
labels.forEach((label) => {
const { top } = DomHandler.getOffset(label);
const threshold = this.getThreshold(label);

if (top - threshold <= windowScrollTop) {
const link = DomHandler.findSingle(label, 'a');
this.activeId = link.id;
}
});
}
});
if (typeof document !== 'undefined') {
const labels = this.getLabels();
const windowScrollTop = DomHandler.getWindowScrollTop();
labels.forEach((label) => {
const { top } = DomHandler.getOffset(label);
const threshold = this.getThreshold(label);

if (top - threshold <= windowScrollTop) {
const link = DomHandler.findSingle(label, 'a');
this.activeId.set(link.id);
}
});
}
}

clearTimeout(this.scrollEndTimer);
Expand All @@ -132,17 +108,15 @@ export class AppDocSectionNavComponent implements OnInit, OnDestroy {
}
}

onButtonClick(event, doc) {
this.activeId = doc.id;
onButtonClick(doc: Doc) {
this.activeId.set(doc.id);
setTimeout(() => {
this.scrollToLabelById(doc.id);
this.isScrollBlocked = true;
}, 1);

event.preventDefault();
}

getThreshold(label) {
getThreshold(label: Element) {
if (typeof document !== undefined) {
if (!this.topbarHeight) {
const topbar = DomHandler.findSingle(document.body, '.layout-topbar');
Expand All @@ -154,7 +128,7 @@ export class AppDocSectionNavComponent implements OnInit, OnDestroy {
return this.topbarHeight + DomHandler.getHeight(label) * 3.5;
}

scrollToLabelById(id) {
scrollToLabelById(id: string) {
if (typeof document !== undefined) {
const label = document.getElementById(id);
this.location.go(this.location.path().split('#')[0] + '#' + id);
Expand All @@ -163,11 +137,4 @@ export class AppDocSectionNavComponent implements OnInit, OnDestroy {
}, 1);
}
}

ngOnDestroy() {
if (this.scrollListener) {
this.scrollListener();
this.scrollListener = null;
}
}
}

0 comments on commit 952aab3

Please sign in to comment.