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

atinc / ngx-tethys / 881c8997-29c3-4d01-9ef1-22092f16cec2

03 Apr 2024 03:31AM UTC coverage: 90.404% (-0.2%) from 90.585%
881c8997-29c3-4d01-9ef1-22092f16cec2

Pull #3062

circleci

minlovehua
refactor(all): use the transform attribute of @input() instead of @InputBoolean() and @InputNumber()
Pull Request #3062: refactor(all): use the transform attribute of @input() instead of @InputBoolean() and @InputNumber()

5411 of 6635 branches covered (81.55%)

Branch coverage included in aggregate %.

217 of 223 new or added lines in 82 files covered. (97.31%)

201 existing lines in 53 files now uncovered.

13176 of 13925 relevant lines covered (94.62%)

980.1 hits per line

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

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

16✔
27
/**
16✔
28
 * 回到顶部组件
16✔
29
 * @name thy-back-top
30
 */
31
@Component({
32
    selector: 'thy-back-top,[thyBackTop]',
33
    templateUrl: './back-top.component.html',
34
    changeDetection: ChangeDetectionStrategy.OnPush,
35
    encapsulation: ViewEncapsulation.None,
16✔
36
    animations: [fadeMotion],
16✔
37
    standalone: true,
16✔
38
    imports: [NgIf, ThyIcon, NgTemplateOutlet]
16✔
39
})
16✔
40
export class ThyBackTop implements OnInit, OnDestroy, OnChanges {
34✔
41
    @HostBinding('class.thy-back-top-container') classNames = true;
10✔
42

43
    /**
44
     * 自定义按钮显示模板
4✔
45
     */
4✔
46
    @Input() thyTemplate?: TemplateRef<void>;
2✔
47

48
    /**
49
     * 指定对哪个 DOM 元素返回顶部
50
     * @type string | HTMLElement
51
     * @default window
15✔
52
     */
53
    @Input() thyContainer?: string | HTMLElement;
54

65✔
55
    /**
56
     * 滚动高度达到此参数值才出现 thy-back-top
57
     * @type number
32✔
58
     */
21✔
59
    @Input({ transform: numberAttribute }) thyVisibilityHeight = 400;
60

11✔
61
    /**
11✔
62
     * 点击按钮的回调函数
11✔
63
     */
2✔
64
    @Output() readonly thyClick: EventEmitter<boolean> = new EventEmitter();
2✔
65

66
    /**
67
     * 监听按钮显示状态的回调函数
68
     */
69
    @Output() public visibleChange: EventEmitter<boolean> = new EventEmitter();
29!
UNCOV
70

×
71
    /** The native `<div class="thy-back-top"></div>` element. */
72
    @ViewChild('backTop', { static: false })
29✔
73
    set backTop(backTop: ElementRef<HTMLElement> | undefined) {
29✔
74
        this.backTop$.next(backTop);
29✔
75
    }
29✔
76

77
    public visible = false;
3✔
78

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

15✔
87
    private destroy$ = new Subject<void>();
14!
88
    private scrollListenerDestroy$ = new Subject<void>();
14✔
89

90
    private target: HTMLElement | null = null;
91

1✔
92
    constructor(
93
        @Inject(DOCUMENT) private doc: any,
94
        private thyScrollService: ThyScrollService,
95
        private platform: Platform,
96
        private cdr: ChangeDetectorRef,
97
        private zone: NgZone
98
    ) {
1✔
99
        this.backTop$
100
            .pipe(
101
                switchMap(backTop =>
102
                    backTop
103
                        ? new Observable(subscriber =>
104
                              zone.runOutsideAngular(() => fromEvent(backTop.nativeElement, 'click').subscribe(subscriber))
105
                          )
106
                        : EMPTY
107
                ),
108
                takeUntil(this.destroy$)
1✔
109
            )
110
            .subscribe(() => {
111
                this.thyScrollService.scrollTo(this.getTarget(), 0);
112
                if (this.thyClick.observers.length) {
113
                    zone.run(() => this.thyClick.emit(true));
114
                }
115
            });
116
    }
117

118
    ngOnInit(): void {
119
        this.registerScrollEvent();
120
    }
121

122
    private getTarget(): HTMLElement | Window {
123
        return this.target || window;
124
    }
125

126
    private handleScroll(): void {
127
        if (this.visible === this.thyScrollService.getScroll(this.getTarget()) > this.thyVisibilityHeight) {
128
            return;
129
        }
130
        this.visible = !this.visible;
131
        this.cdr.detectChanges();
132
        if (this.visibleChange.observers.length > 0) {
133
            this.zone.run(() => {
134
                this.visibleChange.emit(this.visible);
135
            });
136
        }
137
    }
138

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

152
    ngOnDestroy(): void {
153
        this.destroy$.next();
154
        this.scrollListenerDestroy$.next();
155
    }
156

157
    ngOnChanges(changes: any): void {
158
        const { thyContainer } = changes;
159
        if (thyContainer) {
160
            this.target = typeof this.thyContainer === 'string' ? this.doc.querySelector(this.thyContainer) : this.thyContainer;
161
            this.registerScrollEvent();
162
        }
163
    }
164
}
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

© 2025 Coveralls, Inc