Skip to content

Commit f49a11b

Browse files
feat(core): ThyAbstractOverlayConfig 支持 providers 参数并且支持注入dialog 打开的组件 #TINFR-3619 (#3750)
* feat(core): ThyAbstractOverlayConfig支持providers 参数并且支持注入dailog打开的组件 #TINFR-3619 * fix: fix review and add test * fix: fix test * refactor: createInjector 方法改为基类实现
1 parent 5aa2b71 commit f49a11b

File tree

9 files changed

+88
-99
lines changed

9 files changed

+88
-99
lines changed

src/autocomplete/overlay/autocomplete.service.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ import { autocompleteAbstractOverlayOptions } from './autocomplete.options';
3131
@Injectable()
3232
export class ThyAutocompleteService
3333
extends ThyAbstractOverlayService<ThyAutocompleteConfig, ThyAutocompleteContainer>
34-
implements OnDestroy
35-
{
34+
implements OnDestroy {
3635
private scrollDispatcher = inject(ScrollDispatcher);
3736
private ngZone = inject(NgZone);
3837
private _viewportRuler = inject(ViewportRuler);
@@ -101,13 +100,11 @@ export class ThyAutocompleteService
101100
return autocompleteRef;
102101
}
103102

104-
protected createInjector<T>(
105-
config: ThyAutocompleteConfig,
103+
protected createInjectorProviders<T>(
106104
autocompleteRef: ThyAutocompleteRef<T>,
107105
autocompleteContainer: ThyAutocompleteContainer
108-
): Injector {
109-
const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
110-
const injectionTokens: StaticProvider[] = [
106+
): StaticProvider[] {
107+
return [
111108
{
112109
provide: ThyAutocompleteContainer,
113110
useValue: autocompleteContainer
@@ -117,18 +114,6 @@ export class ThyAutocompleteService
117114
useValue: autocompleteRef
118115
}
119116
];
120-
121-
if (config.direction && (!userInjector || !userInjector.get<Directionality | null>(Directionality, null))) {
122-
injectionTokens.push({
123-
provide: Directionality,
124-
useValue: {
125-
value: config.direction,
126-
change: of()
127-
}
128-
});
129-
}
130-
131-
return Injector.create({ parent: userInjector || this.injector, providers: injectionTokens });
132117
}
133118

134119
private originElementAddActiveClass(config: ThyAutocompleteConfig) {

src/core/overlay/abstract-overlay.config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Direction } from '@angular/cdk/bidi';
2-
import { ViewContainerRef } from '@angular/core';
2+
import { StaticProvider, ViewContainerRef } from '@angular/core';
33

44
export interface ThyAbstractOverlayPosition {
55
/** Override for the overlay's top position. */
@@ -149,6 +149,11 @@ export class ThyAbstractOverlayConfig<TData = unknown> {
149149
* @type Node[][]
150150
*/
151151
projectableNodes?: Node[][];
152+
153+
/**
154+
* 设置需要传递的 providers
155+
*/
156+
providers?: StaticProvider[];
152157
}
153158

154159
export interface ThyAbstractOverlayOptions {

src/core/overlay/abstract-overlay.service.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { coerceArray, concatArray, FunctionProp, keyBy } from 'ngx-tethys/util';
2-
import { Subject } from 'rxjs';
3-
2+
import { of, Subject } from 'rxjs';
43
import { ComponentType, Overlay, OverlayConfig, OverlayRef, ScrollStrategy } from '@angular/cdk/overlay';
54
import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
6-
import { Injector, reflectComponentType, TemplateRef } from '@angular/core';
7-
5+
import { Injector, reflectComponentType, StaticProvider, TemplateRef } from '@angular/core';
86
import { SafeAny } from 'ngx-tethys/types';
97
import { ThyAbstractOverlayContainer } from './abstract-overlay-container';
108
import { ThyAbstractOverlayRef } from './abstract-overlay-ref';
119
import { ThyAbstractOverlayConfig, ThyAbstractOverlayOptions } from './abstract-overlay.config';
10+
import { Directionality } from '@angular/cdk/bidi';
1211

1312
export type ComponentTypeOrTemplateRef<T> = ComponentType<T> | TemplateRef<T>;
1413

@@ -25,7 +24,7 @@ export abstract class ThyAbstractOverlayService<TConfig extends ThyAbstractOverl
2524
protected injector: Injector,
2625
protected defaultConfig: TConfig,
2726
public scrollStrategy?: FunctionProp<ScrollStrategy>
28-
) {}
27+
) { }
2928

3029
/** Build cdk overlay config by config */
3130
protected abstract buildOverlayConfig(config: TConfig): OverlayConfig;
@@ -40,12 +39,39 @@ export abstract class ThyAbstractOverlayService<TConfig extends ThyAbstractOverl
4039
config: TConfig
4140
): ThyAbstractOverlayRef<T, TContainer, TResult>;
4241

42+
43+
/** Create injector providers for component content */
44+
protected abstract createInjectorProviders<T>(
45+
overlayRef: ThyAbstractOverlayRef<T, TContainer>,
46+
containerInstance: TContainer
47+
): StaticProvider[];
48+
4349
/** Create injector for component content */
44-
protected abstract createInjector<T>(
50+
protected createInjector<T>(
4551
config: TConfig,
4652
overlayRef: ThyAbstractOverlayRef<T, TContainer>,
4753
containerInstance: TContainer
48-
): Injector;
54+
): Injector {
55+
const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
56+
57+
const injectionProviders = this.createInjectorProviders(overlayRef, containerInstance);
58+
59+
if (config?.providers?.length) {
60+
injectionProviders.unshift(...config.providers)
61+
}
62+
63+
if (config.direction && (!userInjector || !userInjector.get<Directionality | null>(Directionality, null))) {
64+
injectionProviders.push({
65+
provide: Directionality,
66+
useValue: {
67+
value: config.direction,
68+
change: of()
69+
}
70+
});
71+
}
72+
73+
return Injector.create({ parent: userInjector || this.injector, providers: injectionProviders });
74+
};
4975

5076
/** Attach component or template ref to overlay container */
5177
protected attachOverlayContent<T, TResult>(

src/core/test/overlay/abstract-overlay.spec.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class TestDialogContainerComponent<TData = unknown> extends ThyAbstractOv
7575
@ViewChild(CdkPortalOutlet, { static: true })
7676
portalOutlet!: CdkPortalOutlet;
7777

78-
beforeAttachPortal(): void {}
78+
beforeAttachPortal(): void { }
7979

8080
constructor() {
8181
const changeDetectorRef = coreInject(ChangeDetectorRef);
@@ -98,7 +98,7 @@ export class TestDialogContainerComponent<TData = unknown> extends ThyAbstractOv
9898
}
9999
}
100100

101-
abstract class TestDialogRef<T = unknown, TResult = unknown> extends ThyAbstractOverlayRef<T, TestDialogContainerComponent, TResult> {}
101+
abstract class TestDialogRef<T = unknown, TResult = unknown> extends ThyAbstractOverlayRef<T, TestDialogContainerComponent, TResult> { }
102102
class InternalTestDialogRef<T = unknown, TResult = unknown, TData = unknown> extends ThyAbstractInternalOverlayRef<
103103
T,
104104
TestDialogContainerComponent<TData>,
@@ -164,21 +164,17 @@ export class TestDialogService extends ThyAbstractOverlayService<TestDialogConfi
164164
return containerRef.instance;
165165
}
166166

167-
protected createInjector<T>(
168-
config: TestDialogConfig,
167+
protected createInjectorProviders<T>(
169168
abstractOverlayRef: ThyAbstractOverlayRef<T, any>,
170169
containerInstance: TestDialogContainerComponent
171-
): Injector {
172-
const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
173-
174-
const injectionTokens: StaticProvider[] = [
170+
): StaticProvider[] {
171+
return [
175172
{ provide: TestDialogContainerComponent, useValue: containerInstance },
176173
{
177174
provide: TestDialogRef,
178175
useValue: abstractOverlayRef
179176
}
180177
];
181-
return Injector.create({ parent: userInjector || this.injector, providers: injectionTokens });
182178
}
183179

184180
open<T, TData = undefined, TResult = undefined>(
@@ -194,7 +190,7 @@ export class TestDialogService extends ThyAbstractOverlayService<TestDialogConfi
194190
exports: [],
195191
providers: []
196192
})
197-
export class TestDialogModule {}
193+
export class TestDialogModule { }
198194

199195
@Component({
200196
selector: 'test-dialog-basic',

src/dialog/dialog.service.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,28 +56,16 @@ export class ThyDialog extends ThyAbstractOverlayService<ThyDialogConfig, ThyDia
5656
return dialogRef;
5757
}
5858

59-
protected createInjector<T>(config: ThyDialogConfig, dialogRef: ThyDialogRef<T>, dialogContainer: ThyDialogContainer): Injector {
60-
const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
61-
62-
const injectionTokens: StaticProvider[] = [
59+
protected createInjectorProviders<T>(
60+
dialogRef: ThyDialogRef<T>, dialogContainer: ThyDialogContainer
61+
): StaticProvider[] {
62+
return [
6363
{ provide: ThyDialogContainer, useValue: dialogContainer },
6464
{
6565
provide: ThyDialogRef,
6666
useValue: dialogRef
6767
}
6868
];
69-
70-
if (config.direction && (!userInjector || !userInjector.get<Directionality | null>(Directionality, null))) {
71-
injectionTokens.push({
72-
provide: Directionality,
73-
useValue: {
74-
value: config.direction,
75-
change: of()
76-
}
77-
});
78-
}
79-
80-
return Injector.create({ parent: userInjector || this.injector, providers: injectionTokens });
8169
}
8270

8371
constructor() {

src/dialog/test/dialog-test-components.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import {
99
OnInit,
1010
TemplateRef,
1111
ViewContainerRef,
12-
viewChild
12+
viewChild,
13+
InjectionToken
1314
} from '@angular/core';
1415
import { SafeAny } from 'ngx-tethys/types';
1516
import { ThyDialog, ThyDialogModule, ThyDialogRef } from 'ngx-tethys/dialog';
1617
import { ThyButtonModule } from 'ngx-tethys/button';
1718

19+
export const MY_TOKEN = new InjectionToken<string>('MY_TOKEN');
1820
@Component({
1921
selector: 'thy-dialog-content-test-component',
2022
template: ` <div>Hello Dialog <button>Close</button></div> `
@@ -23,8 +25,8 @@ export class DialogSimpleContentTestComponent {
2325
dialogRef = inject<ThyDialogRef<DialogSimpleContentTestComponent>>(ThyDialogRef);
2426
dialogInjector = inject(Injector);
2527
directionality = inject(Directionality);
28+
token = inject(MY_TOKEN, { optional: true })
2629
}
27-
2830
@Component({
2931
selector: 'thy-dialog-full-content-test-component',
3032
template: `
@@ -138,9 +140,9 @@ export class WithInjectedDataDialogTestComponent implements OnInit {
138140

139141
input1 = input();
140142

141-
constructor() {}
143+
constructor() { }
142144

143-
ngOnInit() {}
145+
ngOnInit() { }
144146
}
145147

146148
@Component({
@@ -209,9 +211,9 @@ export class DialogToTopTestComponent implements OnInit {
209211

210212
public viewContainerRef: ViewContainerRef = inject(ViewContainerRef);
211213

212-
constructor() {}
214+
constructor() { }
213215

214-
ngOnInit() {}
216+
ngOnInit() { }
215217

216218
open() {
217219
this.openDialog(PopupFirstTestComponent, 'first', this.viewContainerRef);

src/dialog/test/dialog.spec.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import {
2525
WithInjectedDataDialogTestComponent,
2626
WithOnPushViewContainerTestComponent,
2727
WithTemplateRefTestComponent,
28-
WithViewContainerTestDirective
28+
WithViewContainerTestDirective,
29+
MY_TOKEN
2930
} from './dialog-test-components';
3031
import { provideHttpClient } from '@angular/common/http';
3132
import { provideNoopAnimations } from '@angular/platform-browser/animations';
@@ -1143,7 +1144,7 @@ describe('ThyDialog', () => {
11431144
it('should work with a confirm dialog when onOk callback return undefined', (done: DoneFn) => {
11441145
dialog.confirm({
11451146
content: 'test: ok button return undefined',
1146-
onOk: () => {}
1147+
onOk: () => { }
11471148
});
11481149
assertConfirmBtnWork(done);
11491150
});
@@ -1207,7 +1208,7 @@ describe('ThyDialog', () => {
12071208
it('should show default value', () => {
12081209
dialog.confirm({
12091210
content: 'test: global custom',
1210-
onOk: () => {}
1211+
onOk: () => { }
12111212
});
12121213
viewContainerFixture.detectChanges();
12131214
expect(getConfirmElements().headerTitle.textContent).toBe('全局定义标题');
@@ -1225,7 +1226,7 @@ describe('ThyDialog', () => {
12251226
cancelText: '不了,谢谢',
12261227
okType: 'primary',
12271228
footerAlign: 'right',
1228-
onOk: () => {}
1229+
onOk: () => { }
12291230
});
12301231
viewContainerFixture.detectChanges();
12311232
expect(getConfirmElements().headerTitle.textContent).toBe('自定义标题');
@@ -1238,7 +1239,7 @@ describe('ThyDialog', () => {
12381239
it('should show okText when loading and okLoadingText is not custom', () => {
12391240
const dialogRef = dialog.confirm({
12401241
content: 'test: not custom okLoadingText',
1241-
onOk: () => {}
1242+
onOk: () => { }
12421243
});
12431244
const okButton = getConfirmElements().okButton;
12441245
if (okButton) {
@@ -1253,7 +1254,7 @@ describe('ThyDialog', () => {
12531254
const dialogRef = dialog.confirm({
12541255
content: 'test: custom okLoadingText',
12551256
okLoadingText: '加载中...',
1256-
onOk: () => {}
1257+
onOk: () => { }
12571258
});
12581259
viewContainerFixture.detectChanges();
12591260
// 这个是因为按钮组件在 ngAfterViewInit 钩子中替换了 Dom 元素,如果不 tick 一下加载状态会修改失败
@@ -1478,4 +1479,17 @@ describe('ThyDialog', () => {
14781479
expect(changeDialogs[1].id).toBe('first');
14791480
}));
14801481
});
1482+
1483+
describe('providers option', () => {
1484+
it('should inject providers', fakeAsync(() => {
1485+
const dialogRef = dialog.open(DialogSimpleContentTestComponent, {
1486+
providers: [
1487+
{ provide: MY_TOKEN, useValue: 'my token' }
1488+
]
1489+
});
1490+
1491+
expect(dialogRef.componentInstance.token).toBe('my token');
1492+
1493+
}));
1494+
});
14811495
});

src/popover/popover.service.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -118,28 +118,16 @@ export class ThyPopover extends ThyAbstractOverlayService<ThyPopoverConfig, ThyP
118118
return popoverRef;
119119
}
120120

121-
protected createInjector<T>(config: ThyPopoverConfig, popoverRef: ThyPopoverRef<T>, popoverContainer: ThyPopoverContainer): Injector {
122-
const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
123-
124-
const injectionTokens: StaticProvider[] = [
121+
protected createInjectorProviders<T>(
122+
popoverRef: ThyPopoverRef<T>, popoverContainer: ThyPopoverContainer
123+
): StaticProvider[] {
124+
return [
125125
{ provide: ThyPopoverContainer, useValue: popoverContainer },
126126
{
127127
provide: ThyPopoverRef,
128128
useValue: popoverRef
129129
}
130130
];
131-
132-
if (config.direction && (!userInjector || !userInjector.get<Directionality | null>(Directionality, null))) {
133-
injectionTokens.push({
134-
provide: Directionality,
135-
useValue: {
136-
value: config.direction,
137-
change: of()
138-
}
139-
});
140-
}
141-
142-
return Injector.create({ parent: userInjector || this.injector, providers: injectionTokens });
143131
}
144132

145133
private originElementAddActiveClass(config: ThyPopoverConfig) {

0 commit comments

Comments
 (0)