Skip to content

fix(components/ag-grid): add keyboard commands for row selection header checkbox#4403

Open
johnhwhite wants to merge 2 commits intomainfrom
ag-grid-checkbox-header
Open

fix(components/ag-grid): add keyboard commands for row selection header checkbox#4403
johnhwhite wants to merge 2 commits intomainfrom
ag-grid-checkbox-header

Conversation

@johnhwhite
Copy link
Copy Markdown
Member

@johnhwhite johnhwhite commented May 1, 2026

AB#3963510

Summary by CodeRabbit

  • New Features

    • Added keyboard shortcuts to header row selector (Enter to select all, Space to deselect all).
  • Improvements

    • Migrated internal reactive state to a signals-based approach for more predictable updates.
    • Improved subscription and lifecycle handling for cleaner resource management.
    • Applied column setting updates immutably and made view ID usage dynamic.
  • Tests

    • Added comprehensive tests for header row selector keyboard interactions.

@johnhwhite johnhwhite added the risk level (author): 1 No additional bugs expected from this change label May 1, 2026
@johnhwhite johnhwhite temporarily deployed to e2e-team-members May 1, 2026 23:55 — with GitHub Actions Inactive
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

📝 Walkthrough

Walkthrough

Switched the large data-manager component from BehaviorSubject/imperative updates to Angular Signals/computed gridOptions, introduced a viewId field, replaced HostBinding getters with computed host bindings, made gridSettings reactive via toSignal, and added pause-and-show logic for toggling render. Header-row-selector gained explicit Subscription management, renderer-based keyboard support (Enter/Space) to toggle selectAll/deselectAll, and tests covering keyboard behavior and listener deduplication.

Changes

Data Manager Large — Signals & reactive gridOptions

Layer / File(s) Summary
Imports & Runtime primitives
apps/playground/src/app/components/ag-grid/data-manager-large/data-manager-large.component.ts
Added Signal, signal, computed, toSignal, DestroyRef, and takeUntilDestroyed imports.
Public API / state shape
.../data-manager-large.component.ts
Added public readonly viewId = 'gridView'; replaced isActive$ BehaviorSubject with public readonly isActive = signal(true); changed gridOptions type to Signal<GridOptions>; host class bindings moved from @HostBinding getters to public computed signals.
Reactive wiring
.../data-manager-large.component.ts
Created gridSettingsValue = toSignal(this.gridSettings.valueChanges, { initialValue: ... }); defined this.gridOptions = computed(() => this.#agGridService.getGridOptions(...)) deriving options from gridSettingsValue() and applying immutable column modifications.
Lifecycle / initialization
.../data-manager-large.component.ts
initDataManager and defaultDataState now use this.viewId; removed imperative #applyGridOptions() and gridSettings.setValue(...) calls; added subscription to gridSettings.valueChanges using takeUntilDestroyed(this.#destroyRef) and a #pauseAndShowGrid() method to toggle isActive.
Template binding
apps/playground/src/app/components/ag-grid/data-manager-large/data-manager-large.component.html
Switched template checks from `isActive$

Header Row Selector — keyboard interaction and subscription handling

Layer / File(s) Summary
Internal lifecycle / subscriptions
libs/components/ag-grid/.../header-row-selector.component.ts
Replaced takeUntilDestroyed pattern with explicit #subscriptions: Subscription; create/tear down subscriptions in agInit and on DestroyRef cleanup to avoid stacking listeners.
DOM renderer & keyboard support
libs/components/ag-grid/.../header-row-selector.component.ts
Injected RendererFactory2 and created renderer; when params.eGridHeader exists, sets aria-keyshortcuts="Enter Space" and attaches a keydown listener that, for non-repeated Enter/Space, toggles between selectAll() and deselectAll() (preventing default for Space).
Selection state updates
libs/components/ag-grid/.../header-row-selector.component.ts
Added rowDataUpdated handling to update indeterminate and clear checked when rows change.
Tests
libs/components/ag-grid/.../header-row-selector.component.spec.ts
Added tests verifying Enter/Space keyboard toggles selection as expected, that auto-repeat keydown events are ignored, and that calling agInit multiple times does not stack listeners (checks add/remove call counts).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • Blackbaud-SteveBrush

Poem

A rabbit taps the grid with care,
Signals hum and columns stare,
Enter jumps, Space drifts slow—select and clear,
View IDs tidy, listeners near,
Hooray! the rows now dance for me 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding keyboard commands (Enter/Space) for the row selection header checkbox in ag-grid, which is the primary focus of the component-level changes in header-row-selector.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ag-grid-checkbox-header

Review rate limit: 9/10 reviews remaining, refill in 6 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 1, 2026

View your CI Pipeline Execution ↗ for commit c54a9c7

Command Status Duration Result
nx build code-examples-playground --baseHref=ht... ✅ Succeeded 3m 57s View ↗
nx build playground --baseHref=https://blackbau... ✅ Succeeded 1m 43s View ↗
nx build integration --baseHref=https://blackba... ✅ Succeeded 11s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-02 18:03:00 UTC

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
libs/components/ag-grid/src/lib/modules/ag-grid/header/header-row-selector/header-row-selector.component.spec.ts (1)

203-208: Avoid introducing as any in test setup—use strict typing instead.

Line 208 uses as any, which masks parameter-shape regressions in strict mode. Replace it with a typed IHeaderParams object using as unknown as IHeaderParams to maintain type safety.

Suggested patch
 import {
   GridApi,
+  IHeaderParams,
   IRowNode,
   RowDataUpdatedEvent,
   SelectionChangedEvent,
 } from 'ag-grid-community';
@@
-    component.agInit({
+    const headerParams = {
       api,
       displayName: 'test-selected',
       eGridCell: fixture.nativeElement,
       eGridHeader: headerEl,
-    } as any);
+    } as unknown as IHeaderParams;
+    component.agInit(headerParams);

Per coding guidelines: "Avoid the any type in TypeScript; use unknown when type is uncertain."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@libs/components/ag-grid/src/lib/modules/ag-grid/header/header-row-selector/header-row-selector.component.spec.ts`
around lines 203 - 208, The test is using an unsafe cast with "as any" when
calling component.agInit; replace that with a properly typed header params
object and cast through unknown to satisfy strict typing: construct the object
containing api, displayName, eGridCell, eGridHeader matching the IHeaderParams
shape and call component.agInit(obj as unknown as IHeaderParams) instead of
using "as any" so the test keeps type safety while accommodating the agInit
signature.
apps/playground/src/app/components/ag-grid/data-manager-large/data-manager-large.component.ts (1)

165-165: ⚡ Quick win

Prefer a signal for local active-state toggling instead of BehaviorSubject.

This state is local to the component and mutation-driven; a signal keeps it aligned with the Angular state-management guideline and simplifies updates at Lines 223 and 233.

As per coding guidelines: “Use signals for state management in Angular.”

Also applies to: 223-234

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/playground/src/app/components/ag-grid/data-manager-large/data-manager-large.component.ts`
at line 165, Replace the local BehaviorSubject isActive$ with an Angular signal:
import { signal } from '@angular/core' and declare e.g. public isActive =
signal(false) (drop the $ suffix). Update all places that currently call
isActive$.next(...) to use isActive.set(...) (or isActive.update(...) for
toggles) and reads that used isActive$.value or subscriptions to use isActive()
(or create a computed/readonly signal if you need an observable-like API). Also
remove any subscriptions or teardown logic related to isActive$.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/playground/src/app/components/ag-grid/data-manager-large/data-manager-large.component.ts`:
- Line 165: The grid stays hidden because isActive$ (BehaviorSubject declared as
isActive$) is initialized false; update ngOnInit to call
this.isActive$.next(true) after the data manager is initialized (e.g., after any
initDataManager / data manager setup logic in ngOnInit or the method that
prepares the grid) so the template's `@if` (isActive$ | async) evaluates true on
load and the grid renders immediately.

In
`@libs/components/ag-grid/src/lib/modules/ag-grid/header/header-row-selector/header-row-selector.component.ts`:
- Around line 81-87: Replace the deprecated 'keypress' event with 'keydown' in
the fromEvent call and prevent default for Space to avoid page scrolling: change
fromEvent<KeyboardEvent>(el, 'keypress') to fromEvent<KeyboardEvent>(el,
'keydown'), keep the filter to accept Enter and Space (consider supporting both
' ' and legacy 'Spacebar'), and inside the subscription (where this.checked() is
called) call evt.preventDefault() when evt.key indicates Space before handling
selection; keep existing takeUntilDestroyed, filter, and subscribe flow.

---

Nitpick comments:
In
`@apps/playground/src/app/components/ag-grid/data-manager-large/data-manager-large.component.ts`:
- Line 165: Replace the local BehaviorSubject isActive$ with an Angular signal:
import { signal } from '@angular/core' and declare e.g. public isActive =
signal(false) (drop the $ suffix). Update all places that currently call
isActive$.next(...) to use isActive.set(...) (or isActive.update(...) for
toggles) and reads that used isActive$.value or subscriptions to use isActive()
(or create a computed/readonly signal if you need an observable-like API). Also
remove any subscriptions or teardown logic related to isActive$.

In
`@libs/components/ag-grid/src/lib/modules/ag-grid/header/header-row-selector/header-row-selector.component.spec.ts`:
- Around line 203-208: The test is using an unsafe cast with "as any" when
calling component.agInit; replace that with a properly typed header params
object and cast through unknown to satisfy strict typing: construct the object
containing api, displayName, eGridCell, eGridHeader matching the IHeaderParams
shape and call component.agInit(obj as unknown as IHeaderParams) instead of
using "as any" so the test keeps type safety while accommodating the agInit
signature.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Enterprise

Run ID: 94d05124-8a39-49d4-b7a8-1faef2be7f8f

📥 Commits

Reviewing files that changed from the base of the PR and between b66a41f and 32d90ab.

📒 Files selected for processing (3)
  • apps/playground/src/app/components/ag-grid/data-manager-large/data-manager-large.component.ts
  • libs/components/ag-grid/src/lib/modules/ag-grid/header/header-row-selector/header-row-selector.component.spec.ts
  • libs/components/ag-grid/src/lib/modules/ag-grid/header/header-row-selector/header-row-selector.component.ts

@blackbaud-sky-build-user
Copy link
Copy Markdown
Collaborator

@johnhwhite johnhwhite deployed to e2e-team-members May 2, 2026 17:55 — with GitHub Actions Active
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/playground/src/app/components/ag-grid/data-manager-large/data-manager-large.component.ts (1)

189-205: ⚠️ Potential issue | 🟠 Major

Initialize form values in ngOnInit() after Angular applies @Inputs.

The gridSettings form is built in the constructor using default property values, but @Input properties are assigned by Angular after the constructor completes. This means if a parent component passes bound inputs, the form (and derived gridOptions signal) start with incorrect initial values until form.valueChanges fires.

Move form initialization to ngOnInit() using patchValue() to ensure the form reflects actual input values:

Suggested fix
 public ngOnInit(): void {
+  this.gridSettings.patchValue(
+    {
+      enableTopScroll: this.enableTopScroll,
+      useColumnGroups: this.useColumnGroups,
+      showSelect: this.showSelect,
+      showDelete: this.showDelete,
+      domLayout: this.domLayout,
+      compact: this.compact,
+      wrapText: this.wrapText,
+      autoHeightColumns: this.autoHeightColumns,
+    },
+    { emitEvent: false },
+  );
+
   this.#dataManagerService.initDataManager({

Also applies to lines 263-269.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/playground/src/app/components/ag-grid/data-manager-large/data-manager-large.component.ts`
around lines 189 - 205, The form is being created in the constructor so `@Input`
values set later are not reflected; move the FormBuilder/this.gridSettings
creation and the toSignal setup (gridSettings and gridSettingsValue) into
ngOnInit(), or keep creation minimal in the constructor and in ngOnInit() call
this.gridSettings.patchValue(...) with the actual `@Input-backed` properties and
then initialize/refresh the toSignal-derived gridSettingsValue (and any
dependent gridOptions signal) so the initial form value matches Inputs; update
the same pattern for the similar form block at the second occurrence (lines
~263-269) and ensure ngOnInit contains the initialization/patching logic
referencing gridSettings, gridSettingsValue, and any dependent gridOptions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@apps/playground/src/app/components/ag-grid/data-manager-large/data-manager-large.component.ts`:
- Around line 189-205: The form is being created in the constructor so `@Input`
values set later are not reflected; move the FormBuilder/this.gridSettings
creation and the toSignal setup (gridSettings and gridSettingsValue) into
ngOnInit(), or keep creation minimal in the constructor and in ngOnInit() call
this.gridSettings.patchValue(...) with the actual `@Input-backed` properties and
then initialize/refresh the toSignal-derived gridSettingsValue (and any
dependent gridOptions signal) so the initial form value matches Inputs; update
the same pattern for the similar form block at the second occurrence (lines
~263-269) and ensure ngOnInit contains the initialization/patching logic
referencing gridSettings, gridSettingsValue, and any dependent gridOptions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Enterprise

Run ID: 8eca4aee-2ceb-4799-9379-7b863aff6b50

📥 Commits

Reviewing files that changed from the base of the PR and between 32d90ab and c54a9c7.

📒 Files selected for processing (4)
  • apps/playground/src/app/components/ag-grid/data-manager-large/data-manager-large.component.html
  • apps/playground/src/app/components/ag-grid/data-manager-large/data-manager-large.component.ts
  • libs/components/ag-grid/src/lib/modules/ag-grid/header/header-row-selector/header-row-selector.component.spec.ts
  • libs/components/ag-grid/src/lib/modules/ag-grid/header/header-row-selector/header-row-selector.component.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk level (author): 1 No additional bugs expected from this change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants