• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atinc / ngx-tethys / #55

30 Jul 2025 07:08AM UTC coverage: 9.866% (-80.4%) from 90.297%
#55

push

why520crazy
feat(empty): add setMessage for update display text #TINFR-2616

92 of 6794 branches covered (1.35%)

Branch coverage included in aggregate %.

2014 of 14552 relevant lines covered (13.84%)

6.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

6.9
/src/back-top/back-top.component.ts
1
import {
2
    Component,
3
    OnInit,
4
    ChangeDetectionStrategy,
5
    ViewEncapsulation,
6
    TemplateRef,
7
    HostBinding,
8
    NgZone,
9
    ChangeDetectorRef,
10
    OnDestroy,
11
    OnChanges,
12
    ViewChild,
13
    ElementRef,
14
    numberAttribute,
15
    inject,
1✔
16
    input,
17
    output,
×
18
    effect,
×
19
    viewChild,
×
20
    Signal,
×
21
    computed
×
22
} from '@angular/core';
×
23
import { Subject, fromEvent, BehaviorSubject, EMPTY, Observable } from 'rxjs';
×
24
import { Platform } from '@angular/cdk/platform';
×
25
import { throttleTime, takeUntil, switchMap } from 'rxjs/operators';
×
26
import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
×
27
import { fadeMotion, ThyScrollService } from 'ngx-tethys/core';
×
28
import { ThyIcon } from 'ngx-tethys/icon';
×
29

×
30
/**
31
 * 回到顶部组件
32
 * @name thy-back-top
33
 */
34
@Component({
35
    selector: 'thy-back-top,[thyBackTop]',
36
    templateUrl: './back-top.component.html',
×
37
    changeDetection: ChangeDetectionStrategy.OnPush,
×
38
    encapsulation: ViewEncapsulation.None,
×
39
    animations: [fadeMotion],
×
40
    imports: [ThyIcon, NgTemplateOutlet]
×
41
})
×
42
export class ThyBackTop implements OnInit, OnDestroy {
×
43
    private doc = inject(DOCUMENT);
44
    private thyScrollService = inject(ThyScrollService);
×
45
    private platform = inject(Platform);
×
46
    private cdr = inject(ChangeDetectorRef);
×
47
    private zone = inject(NgZone);
×
48

49
    @HostBinding('class.thy-back-top-container') classNames = true;
50

×
51
    /**
×
52
     * 自定义按钮显示模板
53
     */
×
54
    readonly thyTemplate = input<TemplateRef<void>>();
×
55

56
    /**
57
     * 指定对哪个 DOM 元素返回顶部
58
     * @type string | HTMLElement
×
59
     * @default window
60
     */
61
    readonly thyContainer = input<string | HTMLElement>();
×
62

×
63
    /**
64
     * 滚动高度达到此参数值才出现 thy-back-top
×
65
     * @type number
×
66
     */
×
67
    readonly thyVisibilityHeight = input(400, { transform: numberAttribute });
×
68

69
    /**
70
     * 点击按钮的回调函数
71
     */
×
72
    readonly thyClick = output<boolean>();
×
73

74
    /**
×
75
     * 监听按钮显示状态的回调函数
×
76
     */
×
77
    public readonly visibleChange = output<boolean>();
×
78

79
    /** The native `<div class="thy-back-top"></div>` element. */
×
80
    readonly backTop = viewChild<ElementRef<HTMLElement>>('backTop');
81

82
    public visible = false;
83

×
84
    /**
×
85
     * The subject used to store the native `<div class="thy-back-top"></div>` since
86
     * it's located within the `ngIf` directive. It might be set asynchronously whenever the condition
1✔
87
     * is met. Having subject makes the code reactive and cancellable (e.g. event listeners will be
1✔
88
     * automatically removed and re-added through the `switchMap` below).
89
     */
90
    private backTop$ = new BehaviorSubject<ElementRef<HTMLElement> | undefined>(undefined);
91

92
    private destroy$ = new Subject<void>();
93

94
    private scrollListenerDestroy$ = new Subject<void>();
95

96
    private target: Signal<Element | Window> = computed(() => {
97
        const thyContainerValue = this.thyContainer();
1✔
98
        const target = typeof thyContainerValue === 'string' ? this.doc.querySelector(thyContainerValue) : thyContainerValue;
99
        return target || window;
100
    });
101

102
    constructor() {
103
        const zone = this.zone;
104

105
        this.backTop$
106
            .pipe(
107
                switchMap(backTop =>
108
                    backTop
109
                        ? new Observable(subscriber =>
110
                              zone.runOutsideAngular(() => fromEvent(backTop.nativeElement, 'click').subscribe(subscriber))
111
                          )
112
                        : EMPTY
113
                ),
114
                takeUntil(this.destroy$)
115
            )
116
            .subscribe(() => {
117
                this.thyScrollService.scrollTo(this.target(), 0);
118
                zone.run(() => this.thyClick.emit(true));
119
            });
120

121
        effect(() => {
122
            this.backTop$.next(this.backTop());
123
        });
124
    }
125

126
    ngOnInit(): void {
127
        this.registerScrollEvent();
128
    }
129

130
    private handleScroll(): void {
131
        if (this.visible === this.thyScrollService.getScroll(this.target()) > this.thyVisibilityHeight()) {
132
            return;
133
        }
134
        this.visible = !this.visible;
135
        this.cdr.detectChanges();
136
        this.zone.run(() => {
137
            this.visibleChange.emit(this.visible);
138
        });
139
    }
140

141
    private registerScrollEvent(): void {
142
        if (!this.platform.isBrowser) {
143
            return;
144
        }
145
        this.scrollListenerDestroy$.next();
146
        this.handleScroll();
147
        this.zone.runOutsideAngular(() => {
148
            fromEvent(this.target(), 'scroll', { passive: true })
149
                .pipe(throttleTime(50), takeUntil(this.scrollListenerDestroy$))
150
                .subscribe(() => this.handleScroll());
151
        });
152
    }
153

154
    ngOnDestroy(): void {
155
        this.destroy$.next();
156
        this.scrollListenerDestroy$.next();
157
    }
158
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc