Skip to content

Commit 70f8b6a

Browse files
authored
perf(aria/grid): performance when selecting in a large grid (#32766)
Fixes that the grid cell's host bindings were slowing down the page significantly during selection when placed in a large grid. The issue seems to come from the fact that there's a non-zero cost to the signal reads in the directive's host bindings which can add up. These changes address the issue by batching the reads together in an effect. Fixes #32759.
1 parent 529c1bf commit 70f8b6a

1 file changed

Lines changed: 36 additions & 17 deletions

File tree

src/aria/grid/grid-cell.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {_IdGenerator} from '@angular/cdk/a11y';
1010
import {
11+
afterRenderEffect,
1112
booleanAttribute,
1213
computed,
1314
contentChildren,
@@ -17,6 +18,7 @@ import {
1718
input,
1819
model,
1920
Signal,
21+
Renderer2,
2022
} from '@angular/core';
2123
import {Directionality} from '@angular/cdk/bidi';
2224
import {GridCellPattern} from '../private';
@@ -41,26 +43,11 @@ import {GRID_CELL, GRID_ROW} from './grid-tokens';
4143
@Directive({
4244
selector: '[ngGridCell]',
4345
exportAs: 'ngGridCell',
44-
host: {
45-
'[attr.role]': 'role()',
46-
'[attr.id]': '_pattern.id()',
47-
'[attr.rowspan]': '_pattern.rowSpan()',
48-
'[attr.colspan]': '_pattern.colSpan()',
49-
'[attr.data-active]': 'active()',
50-
'[attr.data-anchor]': '_pattern.anchor()',
51-
'[attr.aria-disabled]': '_pattern.disabled()',
52-
'[attr.aria-rowspan]': '_pattern.rowSpan()',
53-
'[attr.aria-colspan]': '_pattern.colSpan()',
54-
'[attr.aria-rowindex]': '_pattern.ariaRowIndex()',
55-
'[attr.aria-colindex]': '_pattern.ariaColIndex()',
56-
'[attr.aria-selected]': '_pattern.ariaSelected()',
57-
'[tabindex]': '_tabIndex()',
58-
},
5946
providers: [{provide: GRID_CELL, useExisting: GridCell}],
6047
})
6148
export class GridCell {
62-
/** A reference to the host element. */
6349
private readonly _elementRef = inject(ElementRef);
50+
private readonly _renderer = inject(Renderer2);
6451

6552
/** A reference to the host element. */
6653
readonly element = this._elementRef.nativeElement as HTMLElement;
@@ -136,7 +123,39 @@ export class GridCell {
136123
element: () => this.element,
137124
});
138125

139-
constructor() {}
126+
constructor() {
127+
// Note: we don't go through host bindings for these, because the
128+
// effect allows us to batch the reads together which drastically
129+
// improves rendering performance in large grids (see #32759).
130+
afterRenderEffect({
131+
write: () => {
132+
const {_pattern: pattern, _toggleAttribute: toggle} = this;
133+
const rowSpan = pattern.rowSpan();
134+
const colSpan = pattern.colSpan();
135+
toggle('role', this.role());
136+
toggle('id', pattern.id());
137+
toggle('rowspan', rowSpan);
138+
toggle('colspan', rowSpan);
139+
toggle('aria-rowspan', rowSpan);
140+
toggle('aria-colspan', colSpan);
141+
toggle('data-active', this.active());
142+
toggle('data-anchor', pattern.anchor());
143+
toggle('aria-disabled', pattern.disabled());
144+
toggle('aria-rowindex', pattern.ariaRowIndex());
145+
toggle('aria-colindex', pattern.ariaColIndex());
146+
toggle('aria-selected', pattern.ariaSelected());
147+
toggle('tabindex', this._tabIndex());
148+
},
149+
});
150+
}
151+
152+
private _toggleAttribute = (name: string, value: unknown) => {
153+
if (value == null) {
154+
this._renderer.removeAttribute(this.element, name);
155+
} else {
156+
this._renderer.setAttribute(this.element, name, value as string);
157+
}
158+
};
140159

141160
/** Gets the cell widget pattern for a given element. */
142161
private _getWidget(element: Element | null | undefined): any | undefined {

0 commit comments

Comments
 (0)