From 952aab306490395e6b81d26f73a98ea63f81a7ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Can=20=C3=96zkan?= Date: Mon, 9 Dec 2024 11:22:09 +0100 Subject: [PATCH] fix: use rxjs fromEvent instead of renderer.listener to get scroll events (#16896) --- .../doc/app.docsection-nav.component.ts | 161 +++++++----------- 1 file changed, 64 insertions(+), 97 deletions(-) diff --git a/apps/showcase/components/doc/app.docsection-nav.component.ts b/apps/showcase/components/doc/app.docsection-nav.component.ts index ca11ec2e237..d5d8aaa467c 100644 --- a/apps/showcase/components/doc/app.docsection-nav.component.ts +++ b/apps/showcase/components/doc/app.docsection-nav.component.ts @@ -1,52 +1,44 @@ 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: ` ` + template: ` + + ` }) -export class AppDocSectionNavComponent implements OnInit, OnDestroy { - @Input() docs!: Doc[]; - - subscription!: Subscription; - - scrollListener!: any; +export class AppDocSectionNavComponent implements OnInit { + docs = input.required(); - _activeId: any; - - get activeId() { - return this._activeId; - } - - set activeId(val: string) { - if (val !== this._activeId) { - this._activeId = val; - this.cd.markForCheck(); - } - } + activeId = signal(null); isScrollBlocked: boolean = false; @@ -54,47 +46,33 @@ export class AppDocSectionNavComponent implements OnInit, OnDestroy { 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() { @@ -102,23 +80,21 @@ export class AppDocSectionNavComponent implements OnInit, OnDestroy { } 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); @@ -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'); @@ -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); @@ -163,11 +137,4 @@ export class AppDocSectionNavComponent implements OnInit, OnDestroy { }, 1); } } - - ngOnDestroy() { - if (this.scrollListener) { - this.scrollListener(); - this.scrollListener = null; - } - } }