Skip to content

Commit a66045d

Browse files
committed
refactor(cdk/table): avoid circular dependency workaround
The table virtual scroller had a proxy class to work around a circular DI error. These changes rework the usage site so the workaround isn't required anymore.
1 parent 4925d8c commit a66045d

File tree

4 files changed

+16
-65
lines changed

4 files changed

+16
-65
lines changed

src/cdk/table/sticky-position-listener.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
import {InjectionToken} from '@angular/core';
1010

1111
/** The injection token used to specify the StickyPositioningListener. */
12-
export const STICKY_POSITIONING_LISTENER = new InjectionToken<StickyPositioningListener>('CDK_SPL');
12+
export const STICKY_POSITIONING_LISTENER = new InjectionToken<StickyPositioningListener>(
13+
'STICKY_POSITIONING_LISTENER',
14+
);
1315

1416
export type StickySize = number | null | undefined;
1517
export type StickyOffset = number | null | undefined;

src/cdk/table/sticky-styler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class StickyStyler {
6464
private _isBrowser = true,
6565
private readonly _needsPositionStickyOnElement = true,
6666
public direction: Direction,
67-
private readonly _positionListener: StickyPositioningListener,
67+
private readonly _positionListener: StickyPositioningListener | null,
6868
private readonly _tableInjector: Injector,
6969
) {
7070
this._borderCellCss = {

src/cdk/table/table-virtual-scroll.ts

Lines changed: 4 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -25,46 +25,6 @@ import {
2525
} from './sticky-position-listener';
2626
import {_TABLE_VIEW_CHANGE_STRATEGY, CdkTable, RenderRow, RowContext} from './table';
2727

28-
/**
29-
* An implementation of {@link StickyPositioningListener} that forwards sticky updates to another
30-
* listener.
31-
*
32-
* The {@link CdkTableVirtualScroll} directive cannot provide itself as a
33-
* {@link StickyPositioningListener} because the providers for both entities would point to the same
34-
* instance. The {@link CdkTable} depends on the sticky positioning listener and the table virtual
35-
* scroll depends on the table. Since the sticky positioning listener and table virtual scroll would
36-
* be the same instance, this would create a circular dependency.
37-
*
38-
* The {@link CdkTableVirtualScroll} instead provides this class and attaches itself as the
39-
* receiving listener so {@link StickyPositioningListener} and {@link CdkTableVirtualScroll} are
40-
* provided as separate instances.
41-
*
42-
* @docs-private
43-
*/
44-
export class _PositioningListenerProxy implements StickyPositioningListener {
45-
private _listener?: StickyPositioningListener;
46-
47-
setListener(listener: StickyPositioningListener) {
48-
this._listener = listener;
49-
}
50-
51-
stickyColumnsUpdated(update: StickyUpdate): void {
52-
this._listener?.stickyColumnsUpdated(update);
53-
}
54-
55-
stickyEndColumnsUpdated(update: StickyUpdate): void {
56-
this._listener?.stickyEndColumnsUpdated(update);
57-
}
58-
59-
stickyFooterRowsUpdated(update: StickyUpdate): void {
60-
this._listener?.stickyFooterRowsUpdated(update);
61-
}
62-
63-
stickyHeaderRowsUpdated(update: StickyUpdate): void {
64-
this._listener?.stickyHeaderRowsUpdated(update);
65-
}
66-
}
67-
6828
/** @docs-private */
6929
export const _TABLE_VIRTUAL_SCROLL_COLLECTION_VIEWER_FACTORY = () =>
7030
new BehaviorSubject<ListRange>({start: 0, end: 0});
@@ -84,12 +44,10 @@ const SCROLL_SCHEDULER =
8444
*/
8545
@Directive({
8646
selector: 'cdk-table[virtualScroll], table[cdk-table][virtualScroll]',
87-
exportAs: 'cdkVirtualScroll',
47+
exportAs: 'cdkTableVirtualScroll',
8848
providers: [
8949
{provide: _VIEW_REPEATER_STRATEGY, useClass: _RecycleViewRepeaterStrategy},
90-
// The directive cannot provide itself as the sticky positions listener because it introduces
91-
// a circular dependency. Use an intermediate listener as a proxy.
92-
{provide: STICKY_POSITIONING_LISTENER, useClass: _PositioningListenerProxy},
50+
{provide: STICKY_POSITIONING_LISTENER, useExisting: CdkTableVirtualScroll},
9351
// Initially emit an empty range. The virtual scroll viewport will update the range after it is
9452
// initialized.
9553
{
@@ -141,9 +99,6 @@ export class CdkTableVirtualScroll<T>
14199
}
142100

143101
constructor() {
144-
const positioningListener = inject<_PositioningListenerProxy>(STICKY_POSITIONING_LISTENER);
145-
positioningListener.setListener(this);
146-
147102
// Force the table to enable `fixedLayout` to prevent column widths from changing as the user
148103
// scrolls. This also enables caching in the table's sticky styler which reduces calls to
149104
// expensive DOM APIs, such as `getBoundingClientRect()`, and improves overall performance.
@@ -193,13 +148,9 @@ export class CdkTableVirtualScroll<T>
193148
return 0;
194149
}
195150

196-
stickyColumnsUpdated(update: StickyUpdate): void {
197-
// no-op
198-
}
151+
stickyColumnsUpdated(): void {}
199152

200-
stickyEndColumnsUpdated(update: StickyUpdate): void {
201-
// no-op
202-
}
153+
stickyEndColumnsUpdated(): void {}
203154

204155
stickyHeaderRowsUpdated(update: StickyUpdate): void {
205156
this._headerRowStickyUpdates.next(update);

src/cdk/table/table.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -305,11 +305,7 @@ export class CdkTable<T>
305305
private _platform = inject(Platform);
306306
protected _viewRepeater: _ViewRepeater<T, RenderRow<T>, RowContext<T>>;
307307
private readonly _viewportRuler = inject(ViewportRuler);
308-
protected readonly _positioningListener = inject(STICKY_POSITIONING_LISTENER, {optional: true});
309-
protected readonly _parentPositioningListener = inject<StickyPositioningListener>(
310-
STICKY_POSITIONING_LISTENER,
311-
{optional: true, skipSelf: true},
312-
)!;
308+
private _injector = inject(Injector);
313309

314310
private _document = inject(DOCUMENT);
315311

@@ -626,8 +622,6 @@ export class CdkTable<T>
626622
/** Row definition that will only be rendered if there's no data in the table. */
627623
@ContentChild(CdkNoDataRow) _noDataRow: CdkNoDataRow;
628624

629-
private _injector = inject(Injector);
630-
631625
constructor(...args: unknown[]);
632626

633627
constructor() {
@@ -639,7 +633,6 @@ export class CdkTable<T>
639633

640634
this._isServer = !this._platform.isBrowser;
641635
this._isNativeHtmlTable = this._elementRef.nativeElement.nodeName === 'TABLE';
642-
this._parentPositioningListener = this._positioningListener ?? this._parentPositioningListener;
643636
this.viewChange =
644637
this.viewChange ?? new BehaviorSubject<ListRange>({start: 0, end: Number.MAX_VALUE});
645638

@@ -1416,14 +1409,19 @@ export class CdkTable<T>
14161409
*/
14171410
private _setupStickyStyler() {
14181411
const direction: Direction = this._dir ? this._dir.value : 'ltr';
1412+
const injector = this._injector;
1413+
const positioningListener =
1414+
injector.get(STICKY_POSITIONING_LISTENER, null, {optional: true}) ||
1415+
injector.get(STICKY_POSITIONING_LISTENER, null, {optional: true, skipSelf: true});
1416+
14191417
this._stickyStyler = new StickyStyler(
14201418
this._isNativeHtmlTable,
14211419
this.stickyCssClass,
14221420
this._platform.isBrowser,
14231421
this.needsPositionStickyOnElement,
14241422
direction,
1425-
this._parentPositioningListener,
1426-
this._injector,
1423+
positioningListener,
1424+
injector,
14271425
);
14281426
(this._dir ? this._dir.change : observableOf<Direction>())
14291427
.pipe(takeUntil(this._onDestroy))

0 commit comments

Comments
 (0)