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

atinc / ngx-tethys / 68ef226c-f83e-44c1-b8ed-e420a83c5d84

28 May 2025 10:31AM UTC coverage: 10.352% (-80.0%) from 90.316%
68ef226c-f83e-44c1-b8ed-e420a83c5d84

Pull #3460

circleci

pubuzhixing8
chore: xxx
Pull Request #3460: refactor(icon): migrate signal input #TINFR-1476

132 of 6823 branches covered (1.93%)

Branch coverage included in aggregate %.

10 of 14 new or added lines in 1 file covered. (71.43%)

11648 existing lines in 344 files now uncovered.

2078 of 14525 relevant lines covered (14.31%)

6.69 hits per line

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

0.0
/src/core/overlay/abstract-overlay.service.ts
1
import { coerceArray, concatArray, FunctionProp, keyBy } from 'ngx-tethys/util';
2
import { Subject } from 'rxjs';
3

4
import { ComponentType, Overlay, OverlayConfig, OverlayRef, ScrollStrategy } from '@angular/cdk/overlay';
5
import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
6
import { Injector, reflectComponentType, TemplateRef } from '@angular/core';
7

8
import { SafeAny } from 'ngx-tethys/types';
UNCOV
9
import { ThyAbstractOverlayContainer } from './abstract-overlay-container';
×
UNCOV
10
import { ThyAbstractOverlayRef } from './abstract-overlay-ref';
×
UNCOV
11
import { ThyAbstractOverlayConfig, ThyAbstractOverlayOptions } from './abstract-overlay.config';
×
UNCOV
12

×
UNCOV
13
export type ComponentTypeOrTemplateRef<T> = ComponentType<T> | TemplateRef<T>;
×
UNCOV
14

×
UNCOV
15
export abstract class ThyAbstractOverlayService<TConfig extends ThyAbstractOverlayConfig, TContainer extends ThyAbstractOverlayContainer> {
×
UNCOV
16
    protected openedOverlays: ThyAbstractOverlayRef<unknown, TContainer>[] = [];
×
17

18
    private readonly _afterAllClosed = new Subject<void>();
19

20
    private readonly _afterOpened = new Subject<ThyAbstractOverlayRef<any, TContainer>>();
21

UNCOV
22
    constructor(
×
23
        protected options: ThyAbstractOverlayOptions, // component name, e.g: dialog | popover | slide
UNCOV
24
        protected overlay: Overlay,
×
UNCOV
25
        protected injector: Injector,
×
UNCOV
26
        protected defaultConfig: TConfig,
×
27
        public scrollStrategy?: FunctionProp<ScrollStrategy>
UNCOV
28
    ) {}
×
29

30
    /** Build cdk overlay config by config */
31
    protected abstract buildOverlayConfig(config: TConfig): OverlayConfig;
UNCOV
32

×
UNCOV
33
    /** Attach overlay container to overlay*/
×
34
    protected abstract attachOverlayContainer(overlay: OverlayRef, config: TConfig): TContainer;
35

36
    /** Create abstract overlay ref by cdk overlay, container and config  */
37
    protected abstract createAbstractOverlayRef<T, TResult>(
38
        overlayRef: OverlayRef,
UNCOV
39
        containerInstance: TContainer,
×
UNCOV
40
        config: TConfig
×
UNCOV
41
    ): ThyAbstractOverlayRef<T, TContainer, TResult>;
×
UNCOV
42

×
UNCOV
43
    /** Create injector for component content */
×
UNCOV
44
    protected abstract createInjector<T>(
×
UNCOV
45
        config: TConfig,
×
UNCOV
46
        overlayRef: ThyAbstractOverlayRef<T, TContainer>,
×
UNCOV
47
        containerInstance: TContainer
×
UNCOV
48
    ): Injector;
×
UNCOV
49

×
50
    /** Attach component or template ref to overlay container */
51
    protected attachOverlayContent<T, TResult>(
UNCOV
52
        componentOrTemplateRef: ComponentTypeOrTemplateRef<T>,
×
53
        containerInstance: TContainer,
54
        overlayRef: OverlayRef,
55
        config: TConfig
UNCOV
56
    ): ThyAbstractOverlayRef<T, TContainer, TResult> {
×
UNCOV
57
        // Create a reference to the overlay we're creating in order to give the user a handle
×
UNCOV
58
        // to modify and close it.
×
59
        const abstractOverlayRef = this.createAbstractOverlayRef<T, TResult>(overlayRef, containerInstance, config);
UNCOV
60

×
61
        // When the backdrop is clicked, we want to close it.
UNCOV
62
        if (config.hasBackdrop) {
×
63
            overlayRef.backdropClick().subscribe(() => {
64
                if (
UNCOV
65
                    (abstractOverlayRef.disableClose !== undefined && !abstractOverlayRef.disableClose) ||
×
UNCOV
66
                    abstractOverlayRef.backdropClosable
×
UNCOV
67
                ) {
×
UNCOV
68
                    this.close();
×
UNCOV
69
                }
×
70
            });
71
        }
72

73
        if (componentOrTemplateRef instanceof TemplateRef) {
UNCOV
74
            containerInstance.attachTemplatePortal(
×
75
                new TemplatePortal<T>(componentOrTemplateRef, null, <any>{
76
                    $implicit: config.initialState,
UNCOV
77
                    [`${this.options.name}Ref`]: abstractOverlayRef
×
78
                })
79
            );
UNCOV
80
        } else {
×
81
            const injector = this.createInjector<T>(config, abstractOverlayRef, containerInstance);
82
            const contentRef = containerInstance.attachComponentPortal<T>(
83
                new ComponentPortal(componentOrTemplateRef, config.viewContainerRef, injector, undefined, config.projectableNodes)
84
            );
85
            if (config.initialState) {
86
                const metadata = reflectComponentType(componentOrTemplateRef);
87
                const inputsByTemplateName = keyBy(metadata.inputs as SafeAny, 'templateName');
88
                const inputsByPropName = keyBy(metadata.inputs as SafeAny, 'propName');
89
                Object.keys(config.initialState).forEach(key => {
90
                    const value = (config.initialState as SafeAny)[key];
91
                    const input: { templateName?: string; propName?: string } = inputsByTemplateName[key] || inputsByPropName[key];
UNCOV
92
                    if (input) {
×
UNCOV
93
                        contentRef.setInput(input.templateName, value);
×
94
                    } else {
UNCOV
95
                        (contentRef.instance as SafeAny)[key] = value;
×
UNCOV
96
                    }
×
97
                });
98
            }
UNCOV
99
            if (config.hostClass) {
×
UNCOV
100
                const hostClass = coerceArray(config.hostClass);
×
UNCOV
101
                contentRef.location.nativeElement.classList.add(...hostClass);
×
102
            }
UNCOV
103
            abstractOverlayRef.componentInstance = contentRef.instance;
×
UNCOV
104
        }
×
UNCOV
105

×
UNCOV
106
        return abstractOverlayRef;
×
UNCOV
107
    }
×
UNCOV
108

×
UNCOV
109
    protected removeOpenedOverlay(overlayRef: ThyAbstractOverlayRef<any, TContainer>) {
×
110
        const index = this.openedOverlays.indexOf(overlayRef);
UNCOV
111

×
UNCOV
112
        if (index > -1) {
×
113
            this.openedOverlays.splice(index, 1);
114

115
            if (!this.openedOverlays.length) {
116
                this._afterAllClosed.next();
117
            }
118
        }
UNCOV
119
    }
×
120

121
    protected getAbstractOverlayById(id: string): ThyAbstractOverlayRef<any, TContainer> | undefined {
122
        return this.openedOverlays.find(overlay => overlay.id === id);
123
    }
124

125
    protected getAbstractOverlays(): ThyAbstractOverlayRef<any, TContainer>[] {
UNCOV
126
        return this.openedOverlays;
×
127
    }
128

129
    protected buildBaseOverlayConfig(config: TConfig, defaultPanelClass?: string | string[]): OverlayConfig {
130
        const overlayConfig = new OverlayConfig({
131
            positionStrategy: this.overlay.position().global(),
132
            hasBackdrop: config.hasBackdrop,
133
            direction: config.direction,
UNCOV
134
            width: config.width,
×
UNCOV
135
            height: config.height,
×
UNCOV
136
            minWidth: config.minWidth,
×
UNCOV
137
            minHeight: config.minHeight,
×
138
            maxWidth: config.maxWidth,
139
            maxHeight: config.maxHeight,
140
            disposeOnNavigation: config.closeOnNavigation
141
        });
142

143
        if (config.backdropClass) {
144
            overlayConfig.backdropClass = config.backdropClass;
UNCOV
145
        }
×
UNCOV
146

×
147
        overlayConfig.panelClass = concatArray(config.panelClass, defaultPanelClass);
148

UNCOV
149
        return overlayConfig;
×
150
    }
151

152
    protected openOverlay<T, TResult = unknown>(
UNCOV
153
        componentOrTemplateRef: ComponentTypeOrTemplateRef<T>,
×
UNCOV
154
        config?: TConfig
×
UNCOV
155
    ): ThyAbstractOverlayRef<T, TContainer, TResult> {
×
156
        config = { ...this.defaultConfig, ...config };
157
        if (config.id && this.getAbstractOverlayById(config.id)) {
158
            throw Error(`${this.options.name} with id ${config.id} exists already. The ${this.options.name} id must be unique.`);
159
        }
160
        const overlayConfig: OverlayConfig = this.buildOverlayConfig(config);
161
        const overlayRef = this.overlay.create(overlayConfig);
162

163
        const overlayContainer = this.attachOverlayContainer(overlayRef, config);
164
        const abstractOverlayRef = this.attachOverlayContent<T, TResult>(componentOrTemplateRef, overlayContainer, overlayRef, config);
165

166
        this.openedOverlays.push(abstractOverlayRef);
167
        abstractOverlayRef.afterClosed().subscribe(() => {
168
            this.removeOpenedOverlay(abstractOverlayRef);
169
        });
170
        this._afterOpened.next(abstractOverlayRef);
171
        return abstractOverlayRef;
172
    }
173

174
    /**
175
     * 所有弹出框完全关闭后的回调
176
     * @returns
177
     */
178
    afterAllClosed() {
179
        return this._afterAllClosed;
180
    }
181

182
    /**
183
     * 打开弹出框之后的回调
184
     * @returns
185
     */
186
    afterOpened() {
187
        return this._afterOpened;
188
    }
189

190
    /**
191
     * 关闭弹出框,若force为true,则canClose无效,强制关闭
192
     * @param result
193
     * @param force
194
     */
195
    close<T>(result?: T, force?: boolean) {
196
        if (this.openedOverlays.length > 0) {
197
            const lastOverlayRef = this.openedOverlays[this.openedOverlays.length - 1];
198
            if (lastOverlayRef) {
199
                lastOverlayRef.close(result, force);
200
            }
201
        }
202
    }
203

204
    /**
205
     * 关闭所有打开的弹出框
206
     */
207
    closeAll() {
208
        let i = this.openedOverlays.length;
209
        while (i--) {
210
            // 不需要操作 openedOverlays, 因为 close 会触发 afterClosed 的订阅
211
            // 触发订阅后会自动从 openedOverlays 中移除
212
            this.openedOverlays[i].close();
213
        }
214
    }
215

216
    dispose(): void {
217
        this.closeAll();
218
        this._afterAllClosed.complete();
219
        this._afterOpened.complete();
220
    }
221
}
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