Skip to content

Conversation

@riccardoperra
Copy link
Collaborator

@riccardoperra riccardoperra commented Jan 8, 2026

Here's the notable changes:

TLDR

  • Introduced a new createTableHook helper to provide a strongly typed, Angular-friendly table API
  • injectAppTable now returns an enhanced table instance with:
    • typed tableComponents, cellComponents, headerComponents
    • helper functions (appCell, appHeader, appFooter) for typed templates
  • Column definitions can directly reference components defined in createTableHook (e.g. cell.TextCell)
  • Added tanStackTable, tanStackTableCell, tanStackTableHeader, tanStackTableFooter directives to enable context injection in rendered components
  • FlexRenderDirective was refactored to rely entirely on signals. No manual dirty checking anymore!
  • Simplified *flexRenderCell, *flexRenderHeader, and *flexRenderFooter APIs
  • Added injectable tokens for cell / header / footer contexts when using flexRender

FlexRenderCell simplified directive

You can now pass directly a cell, header or footer instance while using *flexRenderCell, *flexRenderHeader or *flexRenderFooter. *flexRender directive still works and can be used when you need to specifiy different props.

<!-- Old -->
<td *flexRender="cell.column.columnDef.cell; props: cell.getContext(); let cell">{{cell}}</td>
<td *flexRender="header.column.columnDef.header; props: header.getContext(); let header">{{header}}</td>
<td *flexRender="footer.column.columnDef.footer; props: footer.getContext(); let footer">{{footer}}</td> 

<!-- New -->
<td *flexRenderCell="cell; let cell">{{cell}}</td>
<th *flexRenderHeader="header; let header">{{header}}</th>
<th *flexRenderFooter="footer; let footer">{{footer}}</th>

New createTableHook helper

Angular doesn’t support higher-order components in the same way React does, but we can still provide a strong, typed authoring experience by leveraging a few Angular-friendly patterns.

export const {
  createAppColumnHelper,
  injectAppTable,
  injectTableContext,
  injectTableCellContext,
  injectTableHeaderContext,
  // injectFlexRenderHeaderContext
  // injectFlexRenderCellContext
} = createTableHook({
  tableComponents: {
    TableToolbar // those will be available through `table` (e.g. table.TableToolbar)
  },
  cellComponents: {
    TextCell // those will be available in columns cell definition (e.g. cell.TextCell)
  },
  headerComponents: {
    FooterSum: // those will be available header/footer definition (e.g. header.FooterSum)
  }
})

In React, context can be injected via components such as AppCell or AppHeader, which act as typed wrappers around render logic:

<table.AppCell cell={c} key={c.id}>
  {(cell) => (
    <td>
      {/* Cell components are pre-bound via AppCell */}
      <cell.FlexRender />
    </td>
  )}
</table.AppCell>

In Angular, the only way to provide contextual bindings to templates is through structural directives. However:

  • Structural directives cannot be created dynamically from a factory function
  • Defining them statically would prevent us from reusing the “app table” typings produced by createTableHook

Because of this, directly emulating the React pattern is not feasible in Angular.

Instead of introducing new directives, we augment the table instance itself with typed helper functions.

This works because:

  • Component bindings for cells, headers, and footers are already attached during table initialization
  • We rely on a custom TanStack Table feature that allows this augmentation at setup time
  • No runtime Object.assign is needed during rendering

This allow us to just call *flexRender and pass that cell in the context, like we did in <V9.

As a recap:

  • Cell object will contain all spreaded cellComponents
  • Header and Footers objects will contain spreaded headerComponents
  • Table object will contain spreaded tableComponents

Column definitions work the same way as before. If a component class or the result of flexRenderComponent is provided, the system renders that component with the correct table context. As a plus, thanks to the new types, you are able to just pass the components defined in createTableHooks

personColumnHelper.accessor('firstName', {
  header: 'First Name',
  footer: ({ header }) => flexRenderComponent(header.FooterColumnId),
  cell: ({ cell }) => flexRenderComponent(cell.TextCell),
})

Using cell/header components in template

What if you need to use header.FooterColumnId in the template? In the react example we have something like this:

<table.AppFooter header={f} key={f.id}>
                      {(footer) => {
                        const columnId = footer.column.id

                        return (
                          <td colSpan={footer.colSpan}>
                            {footer.isPlaceholder ? null : (
                              <>
                                {/* Use FooterSum for numeric columns, FooterColumnId for others */}
                                {columnId === 'age' ||
                                columnId === 'visits' ||
                                columnId === 'progress' ? (
                                    <footer.FooterSum />
                                ) : columnId === 'actions' ? null : (
                                    <footer.FooterColumnId />
                                )}
                              </>
                            )}
                          </td>
                        )
                      }}
                    </table.AppFooter>

In Angular, we achieve the same result by using typed helpers exposed on the table instance.

These helpers (appCell, appHeader, appFooter) simply return casted versions of the original TanStack objects, enriched with the app-specific typings. They are cheap at runtime and do not introduce additional directives or wrappers.

@for (_footer of footerGroup.headers; track _footer.id) {
  @let footer = table.appFooter(_footer); --> this will return an AppHeader type

  <!-- using flexRender -->
  <ng-container *flexRender="footer.FooterSum; props: footer.getContext()"/>;

  <!-- using angular built-in directive -->
  
  <ng-container [tanStackTableFooter]="footer"> <!-- injectable directive that provides footer -->
    <ng-container *ngComponentOutlet="footer.FooterSum"/>
    <!-- if you want to call it like before, 
         you may want to access that context without passing inputs 
     -->
    <app-footer-sum/>
  </ng-container>
}

Context injection via tanStackTableCell / Header / Footer directives

In addition to the typed helpers, this PR introduces the following injectable directives:

  • tanStackTableCell
  • tanStackTableHeader
  • tanStackTableFooter

These directives exist for one specific reason: to allow components rendered inside the table (via ngComponentOutlet or just using the component tag) to inject the correct TanStack Table context. If you use *flexRender, this is already done internally via that directive. This will act like a context in React.

<ng-container [tanStackTable]="table()">
 
 <!-- components can just call injectTableContext() to access table, instead of propagating inputs -->

 <table>
   <thead>
     <th [tanStackTableHeader]="header()">
       <!-- components can just call injectTableHeaderContext() to access header -->
     </th>
   </thead>

   <!-- cell rendering -->
    <tr>
      <td [tanStackTableCell]="cell()"> 
        <!-- components can just call injectTableCellContext() to access cell -->
      </td>
    </tr>
   
 </table>

</ng-container>

FlexRenderDirective rendering refactor

Since the new injectTable with angularReactivityFeature will wraps every method of row, cells etc to a cached computed, the *flexRender directive doesn't need to handle anymore the "mark as dirty" using both ngDoCheck and effects.

The refactored implementation removes ngOnChanges and ngDoCheck using a single effect called during class initialization, which is responsible to handle the entire flow:

  • On the first render, we will create a component/template etc. based on the given inputs. During this phase we will create a nested effect effectwith content(props) as a dependency
  • When that effect changes, we will set the Dirtyflag in order to update the component on the next `FlexRender#update` call
  • When input content or props changes, the nested effect will be destroyed and the render flow will start from scratch. We don't need here to handle component manual dirty checking like before because it will be automatically marked as dirty when you call a computed method inside.

FlexRender new injectable tokens

While using flexRender, based on the given context, components and template and rendered with a custom injector whith those tokens as providers:

While passing a cell context:

  • CellContextToken -> injectTableCellContext() -> returns a signal of a cell instance

While passing an header or footer context

  • HeaderContextToken -> injectTableHeaderContext() -> returns a signal of an header or footer instance

@changeset-bot
Copy link

changeset-bot bot commented Jan 8, 2026

⚠️ No Changeset found

Latest commit: 92f0d47

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

📝 Walkthrough

Walkthrough

Adds a signal-driven FlexRenderDirective and helper directives (FlexRenderCell, TanStackTable/TanStackTableHeader/TanStackTableCell), a createTableHook API for pre-bound table/component wiring, removes the public enableExperimentalReactivity flag, introduces memo metadata, many Angular example projects/components, and corresponding test updates.

Changes

Cohort / File(s) Summary
Flex-render refactor & flags
packages/angular-table/src/flexRender.ts, packages/angular-table/src/flex-render/view.ts, packages/angular-table/src/flex-render/flex-render-component-ref.ts, packages/angular-table/src/flex-render/flags.ts
Rewrote flex-render to a signal/effect-driven directive (renamed FlexRender → FlexRenderDirective), added unmount lifecycle, changed internal flags enum, and moved to constructor DI for component refs.
Context DI helpers
packages/angular-table/src/helpers/cell.ts, packages/angular-table/src/helpers/header.ts, packages/angular-table/src/helpers/table.ts
Added TanStackTable/TanStackTableHeader/TanStackTableCell directives and injection tokens plus injectTableContext/injectTableHeaderContext/injectTableCellContext helpers exposing signals via DI.
createTableHook & helpers
packages/angular-table/src/helpers/createTableHook.ts, packages/angular-table/src/helpers/flexRenderCell.ts, packages/angular-table/src/index.ts
New createTableHook producing createAppColumnHelper and injectAppTable; added FlexRenderCell wrapper directive and exported helper modules and combined FlexRender tuple.
injectTable API & reactivity utils
packages/angular-table/src/injectTable.ts, packages/angular-table/src/reactivityUtils.ts, packages/angular-table/src/angularReactivityFeature.ts
Changed table Subscribe signature to return Signal, changed injectTable defaults/typing, removed public enableExperimentalReactivity option, broadened toComputed/assignReactivePrototypeAPI and simplified isReactive typing.
Memo metadata in table-core
packages/table-core/src/utils.ts
Introduced MemoFnMeta symbol/metadata and getMemoFnMeta; memo now records originalArgsLength via metadata.
FlexRenderCell helper
packages/angular-table/src/helpers/flexRenderCell.ts
New directive for ng-template that maps cell/header/footer inputs to the injected FlexRenderDirective content/props.
Public barrel & package metadata
packages/angular-table/src/index.ts, packages/angular-table/package.json
Reworked public exports (add helpers, remove legacy reactivity exports), added exported FlexRender tuple, bumped Node engine and adjusted Angular peer versions.
Tests added/updated
packages/angular-table/tests/*
Added angularReactivityFeature tests; updated flex-render, injectTable, reactivity utils tests and test utils to reflect new signal/effect behaviors and API changes.
Composable Tables example (new)
examples/angular/composable-tables/... (many files)
New example project with createTableHook-based app, reusable components (cells, headers, toolbar, pagination), data generators, SCSS, build config, and entry/bootstrap files.
Basic App Table example (new)
examples/angular/basic-app-table/... (many files)
New minimal example demonstrating injectAppTable and FlexRenderDirective usage with templates, bootstrapping, and styles.
Example templates & state access updates
examples/angular/* (multiple)
Replaced nested ng-container flexRender usage with structural directives *flexRenderHeader, *flexRenderCell, *flexRenderFooter; migrated many state reads from table.store.state.*table.state().*; removed enableExperimentalReactivity flags.
Column/grouping/resizing example adjustments
examples/angular/column-*/src/*, examples/angular/grouping/src/*
Moved to new Subscribe API for selective state, relocated tableHelper creation in places, removed experimental flags, and updated columnHelper wiring in grouping example.
Remote-data & SSR tweaks
examples/angular/remote-data/src/*, examples/angular/remote-data/tsconfig.json
Loader param renamed requestparams, consolidated SSR provider with withRoutes, changed tsconfig.moduleResolution to bundler, and adjusted catch-all route from /***.
Various example scaffolding files
examples/angular/*/.editorconfig, *.gitignore, angular.json, package.json, tsconfig*.json, index.html, main.ts, styles.scss
New or updated project config, editor, and manifest files for added examples; review build/tsconfig settings and dependencies.

Sequence Diagram(s)

sequenceDiagram
    participant App as App Component
    participant Hook as createTableHook / injectAppTable
    participant DI as TanStackTable Token (DI)
    participant Directive as FlexRenderDirective / FlexRenderCell
    participant View as Signal-driven View

    App->>Hook: call injectAppTable({ columns, data, components })
    Hook->>Hook: register features, row models, component mappings
    Hook-->>App: return table signal instance
    App->>DI: provide table signal via TanStackTable directive
    Directive->>DI: injectTableContext() / injectTableCellContext()
    DI-->>Directive: supply table/header/cell signal
    Directive->>View: create and mount signal-driven view (effect)
    View-->>Directive: on state change -> re-render via signals
    Directive->>View: unmount on destroy
Loading
sequenceDiagram
    participant Table as Table instance
    participant Subscribe as table.Subscribe(selector)
    participant Signal as Selected Signal
    participant Component as UI component

    Component->>Table: call table.Subscribe({ selector })
    Table->>Subscribe: create selected signal
    Subscribe-->>Component: return Signal
    Table->>Signal: update on state change
    Signal-->>Component: component re-evaluates computed/display
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 I hopped through tokens, signals in tow,
New hooks sprouted where columns grow,
Templates trimmed to tidy delight,
Views now wake with each state light,
A joyful hop — composable tables, so! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature addition: introducing composability patterns for Angular tables via the createTableHook helper and related infrastructure.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering all major changes including createTableHook, FlexRenderDirective refactoring, simplified APIs, and context injection patterns.

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

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@nx-cloud
Copy link

nx-cloud bot commented Jan 8, 2026

View your CI Pipeline Execution ↗ for commit 92f0d47


☁️ Nx Cloud last updated this comment at 2026-01-12 22:05:50 UTC

@riccardoperra riccardoperra marked this pull request as ready for review January 11, 2026 21:06
@riccardoperra
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

✅ Actions performed

Full review triggered.

Copy link

@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: 14

Caution

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

⚠️ Outside diff range comments (7)
examples/angular/row-selection/src/app/selection-column.component.ts (1)

48-54: Inconsistent generic constraint and unused properties.

Two issues identified:

  1. Inconsistent generic constraint: TableHeadSelectionComponent uses <T extends RowData> but TableRowSelectionComponent still uses <T>. Apply the same constraint for consistency.

  2. Unused properties: The new cell and row properties are defined but not used in the template. The template still references context.row. Either remove the unused properties or update the template to use the new pattern.

Option A: Remove unused properties if not needed
-export class TableRowSelectionComponent<T> {
-  readonly cell = injectTableCellContext()
-  readonly row = computed(() => this.cell().row)
+export class TableRowSelectionComponent<T extends RowData> {
   context =
     // @ts-expect-error TODO: Should fix types
     injectFlexRenderContext<CellContext<{ rowSelectionFeature: {} }, unknown>>()
 }
Option B: Use the new properties and remove context (preferred if aligning with new pattern)
-export class TableRowSelectionComponent<T> {
+export class TableRowSelectionComponent<T extends RowData> {
   readonly cell = injectTableCellContext()
   readonly row = computed(() => this.cell().row)
-  context =
-    // @ts-expect-error TODO: Should fix types
-    injectFlexRenderContext<CellContext<{ rowSelectionFeature: {} }, unknown>>()
 }

And update the template to use row() instead of context.row:

   template: `
     <input
       type="checkbox"
-      [checked]="context.row.getIsSelected()"
-      (change)="context.row.getToggleSelectedHandler()($event)"
+      [checked]="row().getIsSelected()"
+      (change)="row().getToggleSelectedHandler()($event)"
     />
   `,
packages/angular-table/src/flexRender.ts (1)

236-242: Verify bitwise logic for view recreation check.

The expression FlexRenderFlags.ContentChanged & FlexRenderFlags.ViewFirstRender performs a bitwise AND between the two flag constants, not between this.renderFlags and the flags. If the intent is to check whether either flag is set, this should use OR (|) and include this.renderFlags:

🐛 Proposed fix (if intent is OR)
   #shouldRecreateEntireView() {
     return (
       this.renderFlags &
-      FlexRenderFlags.ContentChanged &
-      FlexRenderFlags.ViewFirstRender
+      (FlexRenderFlags.ContentChanged | FlexRenderFlags.ViewFirstRender)
     )
   }

If the intent is to check that both flags are set simultaneously, the current logic is also incorrect and would need:

return (this.renderFlags & FlexRenderFlags.ContentChanged) && 
       (this.renderFlags & FlexRenderFlags.ViewFirstRender)
packages/angular-table/src/angularReactivityFeature.ts (3)

109-121: Bug: Copy-paste error - uses cell instead of column.

assignColumnPrototype incorrectly references table.options.reactivity?.cell instead of table.options.reactivity?.column:

🐛 Proposed fix
     assignColumnPrototype: (prototype, table) => {
       if (table.options.reactivity?.column === false) {
         return
       }
       markReactive(prototype)
       setReactiveProps(table.get, prototype, {
         skipProperty: getUserSkipPropertyFn(
-          table.options.reactivity?.cell,
+          table.options.reactivity?.column,
           skipBaseProperties,
         ),
         overridePrototype: true,
       })
     },

123-135: Bug: Copy-paste error - uses cell instead of header.

assignHeaderPrototype incorrectly references table.options.reactivity?.cell instead of table.options.reactivity?.header:

🐛 Proposed fix
     assignHeaderPrototype: (prototype, table) => {
       if (table.options.reactivity?.header === false) {
         return
       }
       markReactive(prototype)
       setReactiveProps(table.get, prototype, {
         skipProperty: getUserSkipPropertyFn(
-          table.options.reactivity?.cell,
+          table.options.reactivity?.header,
           skipBaseProperties,
         ),
         overridePrototype: true,
       })
     },

137-149: Bug: Copy-paste error - uses cell instead of row.

assignRowPrototype incorrectly references table.options.reactivity?.cell instead of table.options.reactivity?.row:

🐛 Proposed fix
     assignRowPrototype: (prototype, table) => {
       if (table.options.reactivity?.row === false) {
         return
       }
       markReactive(prototype)
       setReactiveProps(table.get, prototype, {
         skipProperty: getUserSkipPropertyFn(
-          table.options.reactivity?.cell,
+          table.options.reactivity?.row,
           skipBaseProperties,
         ),
         overridePrototype: true,
       })
     },
packages/angular-table/src/flex-render/flags.ts (1)

5-34: Update outdated comment referencing removed flag.

The code consolidation of DirtyCheck and DirtySignal into a single Dirty flag is properly implemented—no actual code references the removed flags. However, the comment at packages/angular-table/src/flexRender.ts:218 still mentions the old DirtySignal flag. Update it to reference the new Dirty flag for accuracy:

- // to react to changes. If the current fn uses signals, we will set the DirtySignal flag
+ // to react to changes. If the current fn uses signals, we will set the Dirty flag
packages/angular-table/tests/injectTable.test.ts (1)

20-49: The whenRenderingDone() promise should be awaited.

fixture.whenRenderingDone() returns a Promise that should be awaited to ensure the test waits for rendering to complete. Without await, the test may complete before rendering finishes, potentially causing flaky tests.

🔧 Proposed fix
-  test('should support required signal inputs', () => {
+  test('should support required signal inputs', async () => {
     @Component({
       selector: 'app-table',
       template: ``,
       standalone: true,
       changeDetection: ChangeDetectionStrategy.OnPush,
     })
     class TableComponent {
       data = input.required<Array<any>>()

       table = injectTable(() => ({
         data: this.data(),
         _features: stockFeatures,
         columns: [],
       }))
     }

     @Component({
       selector: 'app-root',
       imports: [TableComponent],
       template: ` <app-table [data]="[]" /> `,
       changeDetection: ChangeDetectionStrategy.OnPush,
     })
     class RootComponent {}

     const fixture = TestBed.createComponent(RootComponent)
     fixture.detectChanges()

-    fixture.whenRenderingDone()
+    await fixture.whenRenderingDone()
   })
🤖 Fix all issues with AI agents
In @examples/angular/composable-tables/src/app/components/cell-components.ts:
- Around line 59-60: The issue is duplicate Angular component selectors: both
ProgressCell and RowActionsCell use selector 'table-progress-cell', causing a
conflict; update the RowActionsCell @Component selector to a unique value (e.g.,
'table-row-actions-cell') and then update any template references/usages of the
old selector to the new selector so Angular can distinguish the components;
ensure the identifier referenced in the component decorator is changed (the
@Component selector in RowActionsCell) and verify imports/consumers reflect the
new selector.
- Line 24: The template currently calls cell.getValue().toLocaleString() which
will throw if getValue() returns null/undefined; change the template to guard
the value (e.g., use optional chaining and a fallback like
cell.getValue()?.toLocaleString() || '' or compute a safe string in the
component) so that null/undefined values render safely; update the template
expression referencing cell.getValue() and, if needed, add a helper method in
the component to format values before binding.
- Around line 91-105: The PriceCell double-formats the value: the price computed
currently returns a formatted string via toLocaleString while the template
applies the CurrencyPipe; change the computed `price` (in class PriceCell) to
return the raw numeric value (e.g., `() => this.cell().getValue()`) and keep the
`| currency` pipe in the template (remove the toLocaleString formatting),
ensuring `CurrencyPipe` remains in `imports`; alternatively, if you prefer
preformatted strings, remove the `| currency` pipe and `CurrencyPipe` from
`imports` and keep the existing `toLocaleString` in `price`.

In @examples/angular/composable-tables/src/app/components/header-components.ts:
- Around line 26-36: Fix the typo in the FooterColumnId component's host
attribute: change the host key 'tantack-footer-column-id' to the correct
'tanstack-footer-column-id' in the @Component decorator for class FooterColumnId
so the host attribute matches the intended "tanstack" prefix.
- Around line 38-58: The host attribute in FooterSum has a typo and a potential
reactivity hole: change the host key value from "tantack-footer-sum" to
"tanstack-footer-sum", and ensure the computed that builds sum properly depends
on the column id if it can change by referencing it reactively (e.g. capture
this.header().column.id into the computed or use a reactive getter) so that the
sum computed (sum) recalculates when the column id changes; update FooterSum to
reference the corrected host attribute and make the column id a reactive
dependency inside the sum computed.

In @examples/angular/composable-tables/src/app/components/table-components.ts:
- Around line 4-17: The @Component decorator for the table toolbar component is
missing a required selector; add a unique kebab-case selector (e.g.,
"app-table-toolbar") to the @Component metadata so the component can be used in
templates, update any template usages to match that selector, and ensure the
symbol that defines the component (the class tied to this @Component in
table-components.ts) has the selector present in its decorator.
- Around line 24-26: The constructor in the toolbar component is invoking
this.table().resetColumnFilters(), which causes filters to be cleared every time
the component is instantiated; remove that call from the constructor to avoid
the unintended side effect and instead only reset filters via an explicit method
or lifecycle hook if initialization is truly required. Locate the constructor in
table-components.ts and delete the this.table().resetColumnFilters() invocation
(or move it into a clearly named method like resetFilters() or an intentional
init routine that callers invoke).

In @examples/angular/composable-tables/src/app/makeData.ts:
- Around line 45-77: The file defines an unused Product type and the
makeProductData function (symbols: Product, makeProductData, newProduct) which
are not used anywhere in this example; remove the newProduct function, the
Product type and the makeProductData export, or if you intend to keep them for
future use, add a clear comment above newProduct/makeProductData explaining they
are intentionally retained as example helpers for generating product fixtures
and why they are unused now. Ensure any removed exports are also deleted from
the module surface so there are no unused symbol exports.

In @examples/angular/composable-tables/src/app/table.ts:
- Around line 78-80: The default getRowId function (getRowId) used in the table
options assumes a row.id field but Person (from makeData.ts) has no id, causing
undefined row IDs when injectAppTable is called from app.component.ts; update
the default getRowId to safely handle missing ids (for example return row.id ??
index) or make getRowId required so callers must provide one—modify the table
options where getRowId is defined to use a fallback based on the row index (or
add a guard) and ensure app.component.ts either supplies an explicit getRowId or
relies on the new safe default.

In @examples/angular/composable-tables/src/index.html:
- Line 5: The page title is incorrect; update the HTML <title> element in
index.html (currently "Basic") to reflect the example name (e.g., "Composable
Tables" or "composable-tables") so the document title matches the
composable-tables example.
- Line 9: Update the Twind shim import so builds are reproducible by pinning the
CDN version: replace the unpinned script src
"https://cdn.skypack.dev/twind/shim" with the versioned URL
"https://cdn.skypack.dev/[email protected]/shim" in the HTML script tag that
currently loads the Twind shim.

In @packages/angular-table/src/flexRender.ts:
- Line 220: The current condition checks the linkedSignal function object
(this.content) instead of its value, so replace the check in the if statement
that reads `if (!this.#currentEffectRef && typeof this.content === 'function')`
with a check that evaluates the signal's value (e.g., call the signal and test
the returned value: obtain const value = this.content?.(); then use typeof value
=== 'function' or value !== undefined as appropriate) so the branch runs based
on the signal's current value rather than the signal function itself.

In @packages/angular-table/src/helpers/table.ts:
- Around line 5-7: The injection token description for TanStackTableToken is
mislabeled as "HeaderContext"; update the description string used when creating
TanStackTableToken to "[TanStack Table] TableContext" so it correctly reflects
the TanStackTableContext['table'] purpose; locate the token declaration
(TanStackTableToken and TanStackTableContext) and replace the quoted description
accordingly.

In @packages/angular-table/src/lazySignalInitializer.ts:
- Line 9: The constant addedPropsDuringInitialization is declared but never
used; either remove this dead variable or implement property-tracking by adding
a set trap to the Proxy that populates addedPropsDuringInitialization when new
properties are added during initialization. Locate the Proxy creation in
lazySignalInitializer.ts, add a set(target, prop, value) { if (!(prop in
target)) addedPropsDuringInitialization[prop as string] = true; target[prop] =
value; return true; } (or remove the const entirely if tracking is not required)
and ensure any downstream code that expected addedPropsDuringInitialization is
updated accordingly.
🧹 Nitpick comments (36)
examples/angular/composable-tables/.editorconfig (1)

1-16: LGTM! Standard EditorConfig for Angular projects.

The configuration follows EditorConfig conventions and includes appropriate settings for an Angular/TypeScript project. The Markdown exception for trim_trailing_whitespace = false is correct, as trailing spaces are significant in Markdown.

✨ Optional: Add line-ending enforcement

Consider adding end_of_line = lf to the global section to prevent cross-platform line-ending issues:

 [*]
 charset = utf-8
 indent_style = space
 indent_size = 2
 insert_final_newline = true
 trim_trailing_whitespace = true
+end_of_line = lf

This ensures consistent Unix-style line endings across all development environments.

examples/angular/grouping/src/app/app.component.ts (1)

46-53: Consider typing the event parameters.

The event: any typing works but loses type safety. For example code, this is acceptable, but in production code you might use Event and cast event.target appropriately.

Optional type improvement
- onPageInputChange(event: any): void {
+ onPageInputChange(event: Event): void {
-   const page = event.target.value ? Number(event.target.value) - 1 : 0
+   const page = (event.target as HTMLInputElement).value ? Number((event.target as HTMLInputElement).value) - 1 : 0
    this.table.setPageIndex(page)
  }

- onPageSizeChange(event: any) {
+ onPageSizeChange(event: Event) {
-   this.table.setPageSize(Number(event.target.value))
+   this.table.setPageSize(Number((event.target as HTMLSelectElement).value))
  }
examples/angular/row-selection/src/app/app.component.ts (1)

72-72: Remove unused props parameter.

The props parameter is declared but never used. For consistency with line 78 (header: () => 'Last Name'), either use a static string or remove the parameter.

♻️ Suggested fix
-          header: (props) => `First name`,
+          header: () => 'First name',

Or if dynamic rendering isn't required, use a static string like line 102:

-          header: (props) => `First name`,
+          header: 'First name',
examples/angular/composable-tables/README.md (1)

1-3: Consider updating the README title and adding example-specific documentation.

The title "Basic" appears to be leftover from the Angular CLI scaffold generator and doesn't reflect the "composable-tables" example purpose. Consider renaming it and adding a brief description of what this example demonstrates (e.g., the new composable table APIs, FlexRenderCell directives, injectable tokens, etc.).

📝 Suggested improvement
-# Basic
+# Composable Tables Example
+
+This example demonstrates the Angular TanStack Table composable API, including:
+- `FlexRenderCell`, `FlexRenderHeader`, and `FlexRenderFooter` directives
+- Injectable table context tokens (`CellContextToken`, `HeaderContextToken`)
+- Pre-bound table configuration with `createTableHook`

 This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2.
examples/angular/composable-tables/.devcontainer/devcontainer.json (1)

1-4: Consider upgrading to Node.js 20 or 22 LTS.

Node.js 18 LTS reached end-of-life on April 30, 2025. For better long-term support, Node.js 22 LTS (supported until April 2027) is recommended, or Node.js 20 LTS (supported until April 2026) if you prefer to stay closer to the current stable release.

📝 Suggested update
 {
   "name": "Node.js",
-  "image": "mcr.microsoft.com/devcontainers/javascript-node:18"
+  "image": "mcr.microsoft.com/devcontainers/javascript-node:22"
 }
examples/angular/composable-tables/src/styles.scss (1)

120-141: Status badges rely solely on color for differentiation.

For accessibility, consider adding additional visual indicators (icons, patterns, or text prefixes) alongside color for status badges. Users with color vision deficiencies may have difficulty distinguishing between relationship/complicated/single statuses based on color alone.

This is acceptable for an example project, but worth noting for production use.

examples/angular/basic/src/app/app.component.html (3)

6-10: Variable shadowing may cause confusion.

The template variable let header shadows the outer loop variable header from line 6. The inner header contains the rendered content (string/HTML), while the outer header is the header object. This works correctly but may confuse maintainers.

Consider using distinct names like let headerContent for clarity:

Suggested naming for clarity
-              <th *flexRenderHeader="header; let header">
-                <div [innerHTML]="header"></div>
+              <th *flexRenderHeader="header; let headerContent">
+                <div [innerHTML]="headerContent"></div>

19-22: Same shadowing pattern with cell.

Similar to headers, the let cell shadows the outer loop variable. For consistency, consider renaming to let cellContent.


30-33: Shadowing pattern with footer.

Same observation applies here. Consider let footerContent for consistency.

Overall, the new directive-based approach is cleaner and aligns well with the PR's composable tables goal.

examples/angular/column-resizing-performant/src/app/app.component.ts (1)

85-85: Remove unnecessary type assertion.

The as ColumnResizeMode cast is unnecessary since 'onChange' is a valid literal of the ColumnResizeMode type. Type inference works correctly without it, as demonstrated in other framework examples (React, Lit) that use the same value without casting.

Remove the cast
-    columnResizeMode: 'onChange' as ColumnResizeMode,
+    columnResizeMode: 'onChange',
examples/angular/composable-tables/tsconfig.json (1)

1-31: Configuration is consistent with most Angular examples, though some newer examples use the recommended "bundler" strategy.

The file matches the majority of Angular examples in the repository that use "moduleResolution": "node". However, verification shows that 2 newer examples (row-dnd, remote-data) use "moduleResolution": "bundler", which is the recommended approach for modern Angular apps with bundlers. While the current "node" configuration works correctly, consider aligning with the newer pattern for consistency with these recent examples.

packages/angular-table/src/helpers/flexRenderCell.ts (2)

60-81: Consider early-return pattern for mutual exclusivity.

The effect reads all three signals even when only one should be set. If multiple inputs are accidentally provided, the last truthy one silently wins. Consider an early-return pattern for clarity and to avoid redundant signal reads:

♻️ Suggested refactor
     effect(() => {
       const cell = this.cell()
+      if (cell) {
+        this.#flexRender.content.set(cell.column.columnDef.cell)
+        this.#flexRender.props.set(cell.getContext())
+        return
+      }
       const header = this.header()
+      if (header) {
+        this.#flexRender.content.set(header.column.columnDef.header)
+        this.#flexRender.props.set(header.getContext())
+        return
+      }
       const footer = this.footer()
-      const { content, props } = this.#flexRender
-
-      if (cell) {
-        content.set(cell.column.columnDef.cell)
-        props.set(cell.getContext())
-      }
-
-      if (header) {
-        content.set(header.column.columnDef.header)
-        props.set(header.getContext())
-      }
-
       if (footer) {
-        content.set(footer.column.columnDef.footer)
-        props.set(footer.getContext())
+        this.#flexRender.content.set(footer.column.columnDef.footer)
+        this.#flexRender.props.set(footer.getContext())
       }
     })

39-46: Verify directive class naming convention.

The class is named FlexRenderCell but handles cells, headers, and footers. Consider renaming to FlexRenderContext or similar to better reflect its multi-purpose nature, or split into three separate directives for clarity.

packages/angular-table/src/flexRender.ts (1)

322-355: Consider lazy proxy creation for performance.

The #getInjector method creates a new Proxy and Injector on every call. If called frequently during rendering, this could create unnecessary object allocations. Consider caching the injector when props reference hasn't changed.

examples/angular/composable-tables/src/app/app.component.html (1)

14-21: Minor inconsistency in isPlaceholder handling between header and footer.

In the header section (line 15), the @if (!header.isPlaceholder) wraps the entire <th> element, preventing placeholder headers from rendering. In the footer section (line 45), the @if (!footer.isPlaceholder) is placed inside the <th>, meaning placeholder footers still render an empty <th> with colSpan applied.

If this is intentional (e.g., to preserve table column structure in the footer), consider adding a brief comment. Otherwise, consider aligning the patterns:

Option to align footer with header pattern
         @for (footer of footerGroup.headers; track footer.id) {
-          <th [colSpan]="footer.colSpan" [tanStackTableHeader]="footer">
-            @if (!footer.isPlaceholder) {
+          @if (!footer.isPlaceholder) {
+            <th [colSpan]="footer.colSpan" [tanStackTableHeader]="footer">
               <ng-container *flexRenderFooter="footer; let footer">
                 {{ footer }}
               </ng-container>
-            }
-          </th>
+            </th>
+          }
         }

Also applies to: 43-51

packages/angular-table/tests/test-utils.ts (2)

72-99: Consider adding a brief comment explaining the -1 offset.

The logic is correct, but the -1 in getFnArgsLength(fn) - 1 on line 95 may not be immediately obvious to future maintainers. A brief comment explaining that this accounts for the implicit table/context argument would improve clarity.

💡 Suggested improvement
    if (propertyName.startsWith('get')) {
      // Only properties with no arguments are computed
      const fn = testObj[propertyName]
      // Cannot test if is lazy computed since we return the unwrapped value
+     // Subtract 1 to account for the implicit table/context first argument
      const args = Math.max(0, getFnArgsLength(fn) - 1)
      return fn instanceof Function && args === 0
    }

101-103: Accessing private property for testing purposes.

This helper accesses _reactiveCache, which appears to be a private/internal property. While acceptable for testing utilities, be aware this creates a coupling to internal implementation details that may break if the underlying implementation changes.

examples/angular/composable-tables/src/app/components/header-components.ts (1)

1-10: Consider removing commented-out code.

Commented-out React-style code provides no value in this Angular file and adds noise. If this is intended as a reference for future implementation, consider adding a TODO or tracking it in an issue instead.

examples/angular/composable-tables/src/app/app.component.ts (1)

70-79: Consider using readonly for the table property.

For consistency with other properties in this class, table should be marked readonly.

♻️ Suggested change
-  table = injectAppTable(() => ({
+  readonly table = injectAppTable(() => ({
     columns: this.columns,
     data: this.data(),
     debugTable: true,
     // more table options
   }))
examples/angular/composable-tables/src/app/makeData.ts (1)

22-28: Consider using Array.from for cleaner implementation.

This is a minor style preference - the current implementation works correctly.

♻️ Alternative implementation
-const range = (len: number) => {
-  const arr: Array<number> = []
-  for (let i = 0; i < len; i++) {
-    arr.push(i)
-  }
-  return arr
-}
+const range = (len: number) => Array.from({ length: len }, (_, i) => i)
packages/angular-table/tests/angularReactivityFeature.test.ts (2)

44-45: Shared table instance may cause test pollution.

The table instance is created once at module level and shared across all tests in the suite. If any test mutates table state, it could affect subsequent tests. Consider creating the table inside each test or using beforeEach.

♻️ Consider using beforeEach for test isolation
describe('angularReactivityFeature', () => {
  // ... type and column definitions ...
  
  let table: ReturnType<typeof createTestTable>
  
  beforeEach(() => {
    table = createTestTable()
  })
  
  // ... tests ...
})

93-95: Variable headers shadows the outer variable.

The variable headers on line 94 shadows the headers variable from line 78. While this works correctly due to scoping, consider renaming for clarity.

♻️ Suggested rename
-      const headers = headerGroup.headers
-      headers.forEach((header, cellIndex) => {
+      const groupHeaders = headerGroup.headers
+      groupHeaders.forEach((header, headerIndex) => {
examples/angular/composable-tables/src/app/table.ts (1)

33-43: Remove commented-out import block.

These commented-out imports add noise. Remove them or add a TODO if they're planned additions.

packages/angular-table/src/index.ts (2)

6-11: Remove commented-out exports or document deprecation.

The commented-out exports (angularReactivityFeature, lazySignalInitializer, reactivityUtils) suggest these were removed from the public API. Either remove the comments entirely or add a migration note for consumers who may have been using these.


20-20: Document the FlexRender tuple usage pattern.

The FlexRender constant as a tuple of [FlexRenderDirective, FlexRenderCell] is an interesting pattern for Angular imports, but its usage may not be immediately clear to consumers. Consider adding a JSDoc comment explaining how to use it.

📝 Suggested documentation
+/**
+ * FlexRender is a convenience export combining FlexRenderDirective and FlexRenderCell.
+ * Use in component imports: `imports: [...FlexRender]` or `imports: FlexRender`
+ */
 export const FlexRender = [FlexRenderDirective, FlexRenderCell] as const
packages/angular-table/tests/reactivityUtils.test.ts (1)

121-126: Inconsistent use of flushEffects() vs tick().

This test still uses TestBed.flushEffects() while other tests in the same file (lines 29, 33, 37, 171, 173) have been updated to use TestBed.tick(). Consider updating for consistency.

Suggested fix
-        TestBed.flushEffects()
+        TestBed.tick()
         expect(mockSumBy3Fn).toHaveBeenLastCalledWith(4)
         expect(mockSumBy2Fn).toHaveBeenLastCalledWith(3)

         notifier.set(2)
-        TestBed.flushEffects()
+        TestBed.tick()
         expect(mockSumBy3Fn).toHaveBeenLastCalledWith(5)
packages/angular-table/src/helpers/header.ts (1)

17-35: Same standalone consideration as cell.ts.

For consistency with FlexRenderDirective and to explicitly support consumers on Angular versions before 19, consider adding standalone: true to the directive metadata.

Suggested fix
 @Directive({
   selector: '[tanStackTableHeader]',
   exportAs: 'header',
+  standalone: true,
   providers: [
packages/angular-table/src/helpers/cell.ts (1)

17-35: Add standalone: true to the directive decorator for consistency.

The TanStackTableCell directive lacks standalone: true, while the FlexRenderDirective in src/flexRender.ts explicitly includes it. All helper directives (cell.ts, header.ts, table.ts, flexRenderCell.ts) are currently missing this configuration, which should be added for consistency across the codebase and to ensure compatibility with Angular versions prior to 19.

Suggested fix
 @Directive({
   selector: '[tanStackTableCell]',
   exportAs: 'cell',
+  standalone: true,
   providers: [
packages/angular-table/src/injectTable.ts (1)

117-119: Consider adding writable: false for immutability clarity.

Using Object.defineProperty without specifying writable defaults to false, which is correct here. However, explicitly setting writable: false would improve readability and make the intent clearer.

Suggested change
     Object.defineProperty(table, 'state', {
       value: injectStore(table.store, selector, { injector }),
+      writable: false,
     })
packages/angular-table/src/helpers/createTableHook.ts (4)

312-318: Type safety loss with any for TData parameter.

Both injectTableHeaderContext and injectTableCellContext pass any as the TData type parameter, which loses type safety. Consider using a generic or keeping the type parameter open for consumers to specify.

Suggested improvement
-  function injectTableHeaderContext<TValue extends CellData = CellData>() {
-    return _injectTableHeaderContext<TFeatures, any, TValue>()
+  function injectTableHeaderContext<
+    TData extends RowData = RowData,
+    TValue extends CellData = CellData,
+  >() {
+    return _injectTableHeaderContext<TFeatures, TData, TValue>()
   }

-  function injectTableCellContext<TValue extends CellData = CellData>() {
-    return _injectTableCellContext<TFeatures, any, TValue>()
+  function injectTableCellContext<
+    TData extends RowData = RowData,
+    TValue extends CellData = CellData,
+  >() {
+    return _injectTableCellContext<TFeatures, TData, TValue>()
   }

360-368: Overly broad type cast loses type information.

The return cast as AngularTable<any, any> discards the type parameters that were carefully constructed. This could lead to type errors downstream.

Suggested fix
     return injectTable<TFeatures, TData, TSelected>(() => {
       const options = {
         ...defaultTableOptions,
         ...tableOptions(),
       } as TableOptions<TFeatures, TData>
       options._features = { ...options._features, appTableFeatures }
       return options
-    }, selector) as AngularTable<any, any>
+    }, selector) as AppAngularTable<
+      TFeatures,
+      TData,
+      TSelected,
+      TTableComponents,
+      TCellComponents,
+      THeaderComponents
+    >
   }

53-54: Consider a more specific return type for FlexRender.

The FlexRender property on both cell and header has return type unknown. If possible, provide a more specific type to improve developer experience.

Also applies to: 73-74


376-384: Document the type-only nature of this wrapper.

The comment on line 376-377 explains the runtime behavior, but a JSDoc comment on the function would help API consumers understand that this is primarily a type enhancement over the core column helper.

Suggested documentation
+  /**
+   * Creates a column helper with enhanced types for pre-bound components.
+   * At runtime, this delegates to the core column helper - the enhancement is purely at the type level.
+   */
   function createAppColumnHelper<TData extends RowData>(): AppColumnHelper<
     TFeatures,
     TData,
     TCellComponents,
     THeaderComponents
   > {
packages/angular-table/src/reactivityUtils.ts (1)

173-187: Cache key collision risk and repeated metadata assignment.

Two concerns:

  1. The cache key ${self.id}${fnName} could collide if id contains characters that match fnName (e.g., id="getState" and fnName="Value" vs id="get" and fnName="StateValue").
  2. markReactive and $internalMemoFnMeta assignment happen on every property access, not just initial creation.
Suggested improvements
-        self._reactiveCache ??= {}
-        const cached = (self._reactiveCache[`${self.id}${fnName}`] ??= computed(
+        const cacheKey = `${fnName}:${self.id}`
+        self._reactiveCache ??= {}
+        let cached = self._reactiveCache[cacheKey]
+        if (!cached) {
+          cached = computed(
           () => {
             notifier()
             return fn.apply(self)
           },
           {},
-        ))
-        markReactive(cached)
-        cached[$internalMemoFnMeta] = {
-          originalArgsLength,
-        } satisfies MemoFnMeta
+          )
+          markReactive(cached)
+          cached[$internalMemoFnMeta] = {
+            originalArgsLength,
+          } satisfies MemoFnMeta
+          self._reactiveCache[cacheKey] = cached
+        }
         return cached
examples/angular/composable-tables/src/app/components/cell-components.ts (2)

72-88: Consider extracting repeated row access to a computed property.

The firstName and lastName access pattern is repeated across view(), edit(), and delete(). Extracting to a computed would be cleaner.

Suggested refactor
 export class RowActionsCell {
   readonly cell = injectTableCellContext<number>()

+  readonly fullName = computed(
+    () => `${this.cell().row.original.firstName} ${this.cell().row.original.lastName}`
+  )
+
   view() {
-    alert(
-      `View: ${this.cell().row.original.firstName} ${this.cell().row.original.lastName}`,
-    )
+    alert(`View: ${this.fullName()}`)
   }

   edit() {
-    alert(
-      `Edit: ${this.cell().row.original.firstName} ${this.cell().row.original.lastName}`,
-    )
+    alert(`Edit: ${this.fullName()}`)
   }

   delete() {
-    alert(
-      `Delete: ${this.cell().row.original.firstName} ${this.cell().row.original.lastName}`,
-    )
+    alert(`Delete: ${this.fullName()}`)
   }
 }

7-17: Inconsistent context injection patterns.

TextCell and NumberCell use injectFlexRenderContext<CellContext<...>>() while StatusCell and others use injectTableCellContext<...>(). This inconsistency could confuse developers. Consider documenting when to use which, or standardizing on one approach.

Also applies to: 31-44

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41501b8 and 6fb4bac.

⛔ Files ignored due to path filters (2)
  • examples/angular/composable-tables/src/favicon.ico is excluded by !**/*.ico
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (64)
  • examples/angular/basic/src/app/app.component.html
  • examples/angular/column-ordering/src/app/app.component.ts
  • examples/angular/column-pinning-sticky/src/app/app.component.ts
  • examples/angular/column-pinning/src/app/app.component.ts
  • examples/angular/column-resizing-performant/src/app/app.component.ts
  • examples/angular/column-visibility/src/app/app.component.ts
  • examples/angular/composable-tables/.devcontainer/devcontainer.json
  • examples/angular/composable-tables/.editorconfig
  • examples/angular/composable-tables/.gitignore
  • examples/angular/composable-tables/README.md
  • examples/angular/composable-tables/angular.json
  • examples/angular/composable-tables/package.json
  • examples/angular/composable-tables/src/app/app.component.html
  • examples/angular/composable-tables/src/app/app.component.ts
  • examples/angular/composable-tables/src/app/app.config.ts
  • examples/angular/composable-tables/src/app/app.routes.ts
  • examples/angular/composable-tables/src/app/components/cell-components.ts
  • examples/angular/composable-tables/src/app/components/header-components.ts
  • examples/angular/composable-tables/src/app/components/table-components.ts
  • examples/angular/composable-tables/src/app/makeData.ts
  • examples/angular/composable-tables/src/app/table.ts
  • examples/angular/composable-tables/src/assets/.gitkeep
  • examples/angular/composable-tables/src/index.html
  • examples/angular/composable-tables/src/main.ts
  • examples/angular/composable-tables/src/styles.scss
  • examples/angular/composable-tables/tsconfig.app.json
  • examples/angular/composable-tables/tsconfig.json
  • examples/angular/composable-tables/tsconfig.spec.json
  • examples/angular/editable/src/app/app.component.html
  • examples/angular/expanding/src/app/app.component.ts
  • examples/angular/filters/src/app/app.component.ts
  • examples/angular/grouping/src/app/app.component.ts
  • examples/angular/grouping/src/app/columns.ts
  • examples/angular/remote-data/src/app/app.component.ts
  • examples/angular/remote-data/src/app/app.config.server.ts
  • examples/angular/remote-data/src/app/app.routes.server.ts
  • examples/angular/remote-data/src/server.ts
  • examples/angular/remote-data/tsconfig.json
  • examples/angular/row-selection-signal/src/app/app.component.ts
  • examples/angular/row-selection/src/app/app.component.html
  • examples/angular/row-selection/src/app/app.component.ts
  • examples/angular/row-selection/src/app/selection-column.component.ts
  • examples/angular/signal-input/src/app/person-table/person-table.component.ts
  • packages/angular-table/package.json
  • packages/angular-table/src/angularReactivityFeature.ts
  • packages/angular-table/src/flex-render/flags.ts
  • packages/angular-table/src/flex-render/flex-render-component-ref.ts
  • packages/angular-table/src/flex-render/view.ts
  • packages/angular-table/src/flexRender.ts
  • packages/angular-table/src/helpers/cell.ts
  • packages/angular-table/src/helpers/createTableHook.ts
  • packages/angular-table/src/helpers/flexRenderCell.ts
  • packages/angular-table/src/helpers/header.ts
  • packages/angular-table/src/helpers/table.ts
  • packages/angular-table/src/index.ts
  • packages/angular-table/src/injectTable.ts
  • packages/angular-table/src/lazySignalInitializer.ts
  • packages/angular-table/src/reactivityUtils.ts
  • packages/angular-table/tests/angularReactivityFeature.test.ts
  • packages/angular-table/tests/flex-render/flex-render.unit.test.ts
  • packages/angular-table/tests/injectTable.test.ts
  • packages/angular-table/tests/reactivityUtils.test.ts
  • packages/angular-table/tests/test-utils.ts
  • packages/table-core/src/utils.ts
💤 Files with no reviewable changes (3)
  • examples/angular/row-selection-signal/src/app/app.component.ts
  • examples/angular/signal-input/src/app/person-table/person-table.component.ts
  • examples/angular/filters/src/app/app.component.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-15T15:05:14.117Z
Learnt from: stevenwjy
Repo: TanStack/table PR: 6094
File: packages/table-core/src/utils/getGroupedRowModel.ts:74-78
Timestamp: 2025-09-15T15:05:14.117Z
Learning: In TanStack Table's Row interface, the `subRows` property is typed as `Row<TData>[]` (not optional) and the `createRow` function always initializes it as an empty array, so `subRows` is guaranteed to never be undefined.

Applied to files:

  • packages/angular-table/src/helpers/table.ts
  • packages/angular-table/src/helpers/header.ts
  • packages/angular-table/src/helpers/cell.ts
  • examples/angular/row-selection/src/app/selection-column.component.ts
  • packages/angular-table/src/flexRender.ts
  • examples/angular/grouping/src/app/app.component.ts
📚 Learning: 2025-09-15T15:05:14.117Z
Learnt from: stevenwjy
Repo: TanStack/table PR: 6094
File: packages/table-core/src/utils/getGroupedRowModel.ts:74-78
Timestamp: 2025-09-15T15:05:14.117Z
Learning: In TanStack Table's Row interface, the `subRows` property is typed as `Row<TData>[]` (not optional) and the `createRow` function always initializes it as an empty array using `subRows ?? []`, so `subRows` is guaranteed to never be undefined.

Applied to files:

  • packages/angular-table/src/helpers/table.ts
  • packages/angular-table/src/helpers/cell.ts
  • examples/angular/row-selection/src/app/selection-column.component.ts
  • packages/angular-table/src/flexRender.ts
  • examples/angular/grouping/src/app/app.component.ts
🧬 Code graph analysis (22)
examples/angular/composable-tables/src/app/app.config.ts (1)
examples/angular/composable-tables/src/app/app.routes.ts (1)
  • routes (3-3)
examples/angular/composable-tables/src/main.ts (1)
examples/angular/composable-tables/src/app/app.config.ts (1)
  • appConfig (5-7)
packages/angular-table/src/helpers/flexRenderCell.ts (3)
packages/angular-table/src/helpers/cell.ts (1)
  • Directive (17-35)
packages/angular-table/src/helpers/header.ts (1)
  • Directive (17-35)
packages/angular-table/src/flex-render/view.ts (2)
  • content (56-58)
  • content (60-63)
examples/angular/composable-tables/src/app/components/header-components.ts (2)
examples/angular/composable-tables/src/app/app.component.ts (1)
  • Component (17-80)
packages/angular-table/src/helpers/header.ts (1)
  • injectTableHeaderContext (37-43)
examples/angular/remote-data/src/app/app.config.server.ts (1)
examples/angular/remote-data/src/app/app.routes.server.ts (1)
  • serverRoutes (4-9)
packages/angular-table/tests/angularReactivityFeature.test.ts (4)
packages/table-core/src/types/ColumnDef.ts (1)
  • ColumnDef (202-209)
packages/table-core/src/features/stockFeatures.ts (1)
  • stockFeatures (33-48)
packages/angular-table/src/injectTable.ts (1)
  • injectTable (40-123)
packages/angular-table/tests/test-utils.ts (2)
  • testShouldBeComputedProperty (72-99)
  • getFnReactiveCache (101-103)
packages/angular-table/tests/test-utils.ts (1)
packages/table-core/src/utils.ts (1)
  • getMemoFnMeta (76-78)
packages/angular-table/src/flex-render/flex-render-component-ref.ts (1)
packages/angular-table/src/flex-render/flex-render-component.ts (1)
  • FlexRenderComponent (94-119)
examples/angular/composable-tables/src/app/table.ts (6)
packages/table-core/src/helpers/tableFeatures.ts (1)
  • tableFeatures (14-18)
packages/table-core/src/features/row-sorting/createSortedRowModel.ts (1)
  • createSortedRowModel (12-30)
packages/table-core/src/fns/sortFns.ts (1)
  • sortFns (164-171)
packages/table-core/src/features/column-filtering/createFilteredRowModel.ts (1)
  • createFilteredRowModel (22-44)
packages/table-core/src/fns/filterFns.ts (1)
  • filterFns (358-371)
packages/table-core/src/features/row-pagination/createPaginatedRowModel.ts (1)
  • createPaginatedRowModel (10-30)
examples/angular/composable-tables/src/app/components/cell-components.ts (1)
examples/angular/composable-tables/src/app/components/table-components.ts (1)
  • Component (4-27)
packages/angular-table/src/helpers/header.ts (4)
packages/table-core/src/types/Header.ts (1)
  • Header (23-37)
packages/angular-table/src/flexRender.ts (1)
  • Directive (65-356)
packages/angular-table/src/helpers/cell.ts (1)
  • Directive (17-35)
packages/angular-table/src/helpers/table.ts (1)
  • Directive (16-33)
packages/angular-table/tests/injectTable.test.ts (2)
examples/angular/basic/src/app/app.component.ts (1)
  • Component (78-99)
packages/vue-table/src/merge-proxy.ts (1)
  • keys (72-77)
packages/angular-table/src/helpers/cell.ts (6)
packages/table-core/src/types/TableFeatures.ts (1)
  • TableFeatures (42-43)
packages/table-core/src/types/type-utils.ts (2)
  • RowData (5-5)
  • CellData (7-7)
packages/table-core/src/types/Cell.ts (1)
  • Cell (22-33)
packages/angular-table/src/flexRender.ts (1)
  • Directive (65-356)
packages/angular-table/src/helpers/header.ts (1)
  • Directive (17-35)
packages/angular-table/src/helpers/table.ts (1)
  • Directive (16-33)
examples/angular/row-selection/src/app/selection-column.component.ts (3)
packages/table-core/src/types/type-utils.ts (1)
  • RowData (5-5)
packages/table-core/src/core/headers/coreHeadersFeature.types.ts (1)
  • HeaderContext (30-47)
packages/angular-table/src/helpers/cell.ts (1)
  • injectTableCellContext (37-43)
examples/angular/row-selection/src/app/app.component.ts (2)
packages/angular-table/src/index.ts (1)
  • FlexRender (20-20)
packages/angular-table/src/flexRender.ts (1)
  • flexRenderComponent (293-306)
packages/angular-table/tests/reactivityUtils.test.ts (1)
packages/angular-table/src/reactivityUtils.ts (1)
  • toComputed (83-145)
packages/angular-table/src/helpers/createTableHook.ts (6)
packages/angular-table/src/flexRender.ts (1)
  • FlexRenderContent (48-56)
packages/table-core/src/core/cells/coreCellsFeature.types.ts (1)
  • CellContext (8-19)
packages/table-core/src/types/TableOptions.ts (1)
  • TableOptions (51-98)
packages/angular-table/src/helpers/table.ts (1)
  • injectTableContext (35-40)
packages/angular-table/src/helpers/header.ts (1)
  • injectTableHeaderContext (37-43)
packages/angular-table/src/helpers/cell.ts (1)
  • injectTableCellContext (37-43)
examples/angular/column-resizing-performant/src/app/app.component.ts (2)
packages/angular-table/src/injectTable.ts (1)
  • injectTable (40-123)
packages/table-core/src/features/column-resizing/columnResizingFeature.types.ts (1)
  • ColumnResizeMode (16-16)
packages/angular-table/src/flexRender.ts (7)
packages/angular-table/src/helpers/cell.ts (2)
  • Directive (17-35)
  • TanStackTableCellToken (13-15)
packages/angular-table/src/helpers/header.ts (2)
  • Directive (17-35)
  • TanStackTableHeaderToken (5-7)
packages/angular-table/src/helpers/table.ts (2)
  • Directive (16-33)
  • TanStackTableToken (5-7)
packages/table-core/src/types/type-utils.ts (2)
  • RowData (5-5)
  • CellData (7-7)
packages/table-core/src/core/cells/coreCellsFeature.types.ts (1)
  • CellContext (8-19)
packages/table-core/src/core/headers/coreHeadersFeature.types.ts (1)
  • HeaderContext (30-47)
packages/angular-table/src/flex-render/view.ts (4)
  • previousContent (52-54)
  • content (56-58)
  • content (60-63)
  • FlexRenderComponentView (109-164)
examples/angular/composable-tables/src/app/components/table-components.ts (3)
examples/angular/composable-tables/src/app/app.component.ts (1)
  • Component (17-80)
examples/angular/composable-tables/src/app/components/header-components.ts (2)
  • Component (26-36)
  • Component (38-58)
packages/angular-table/src/helpers/table.ts (1)
  • injectTableContext (35-40)
packages/angular-table/src/reactivityUtils.ts (1)
packages/table-core/src/utils.ts (3)
  • getMemoFnMeta (76-78)
  • $internalMemoFnMeta (63-63)
  • MemoFnMeta (64-64)
packages/angular-table/tests/flex-render/flex-render.unit.test.ts (1)
packages/angular-table/src/index.ts (1)
  • FlexRender (20-20)

@riccardoperra
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
packages/angular-table/src/flexRender.ts (1)

239-245: Potential logic bug: Bitwise AND may not match intended behavior.

The condition uses bitwise AND across three values:

this.renderFlags & FlexRenderFlags.ContentChanged & FlexRenderFlags.ViewFirstRender

This evaluates as (renderFlags & ContentChanged) & ViewFirstRender, which only returns truthy if the resulting bits overlap with ViewFirstRender. If ContentChanged and ViewFirstRender are different bit positions (e.g., 0b01 and 0b10), this will always be 0.

Compare with update() at line 176 which uses OR: ContentChanged | ViewFirstRender.

If the intent is to check whether either flag is set, use OR:

Proposed fix
 #shouldRecreateEntireView() {
   return (
     this.renderFlags &
-    FlexRenderFlags.ContentChanged &
-    FlexRenderFlags.ViewFirstRender
+    (FlexRenderFlags.ContentChanged | FlexRenderFlags.ViewFirstRender)
   )
 }
🤖 Fix all issues with AI agents
In @examples/angular/basic-app-table/package.json:
- Line 10: The package.json "lint" script invokes ESLint but the project lacks
ESLint packages in devDependencies; update package.json devDependencies to
include eslint plus the Angular ESLint packages (e.g., eslint,
@angular-eslint/eslint-plugin, @angular-eslint/eslint-plugin-template,
@angular-eslint/template-parser and any peer packages needed) and then run
npm/yarn install so the "lint" script succeeds; ensure the versions align with
the project's Angular/TypeScript versions and the existing "lint" script remains
unchanged.
- Around line 14-21: The package.json pins Angular 21 packages (e.g.,
"@angular/core", "@angular/common", etc.) while using an alpha of TanStack
Angular Table ("@tanstack/angular-table": "^9.0.0-alpha.10") which lacks
official Angular 21 support; either align versions by downgrading Angular
packages to a supported major (e.g., Angular 17) or bump the table adapter to a
stable release that documents Angular 21 compatibility, and update the
"dependencies" entries for "@angular/*" or "@tanstack/angular-table" accordingly
after validating compatibility in local testing.

In @examples/angular/basic-app-table/src/app/app.component.ts:
- Around line 116-118: The rerender method mutates the module-level defaultData
because defaultData.sort(() => -1) sorts in-place and uses an invalid
comparator; change rerender to operate on a copy and use a stable operation —
e.g. make a shallow copy of defaultData (via slice or spread) and then reverse
or sort with a proper comparator before calling this.data.set; update the
rerender function reference accordingly to avoid mutating defaultData.

In @examples/angular/basic/src/app/app.component.ts:
- Around line 5-6: The top-of-file comment incorrectly refers to the `useTable`
hook; update that comment to reference `injectTable` so it matches the actual
API used in this file (the code calls `injectTable`). Locate the comment near
the file header and change the text from "useTable" to "injectTable", keeping
the rest of the explanatory sentence intact for consistency.

In @examples/angular/composable-tables/src/app/components/cell-components.ts:
- Line 24: The template calls cell.getValue().toLocaleString() which can throw
if getValue() is null/undefined; guard the call by checking the value first (use
optional chaining and nullish coalescing or a ternary) so you only call
toLocaleString() when cell.getValue() != null and render an empty string (or
fallback) otherwise; update the template in cell-components.ts around the
template where cell.getValue() is used to use something like
cell.getValue()?.toLocaleString() ?? '' or an equivalent null check/ternary.

In @packages/angular-table/src/flexRender.ts:
- Around line 311-323: The #renderCustomComponent method uses this.injector()
directly when calling #flexRenderComponentFactory.createComponent, but it builds
a merged injector via this.#getInjector(this.injector()) (like #renderComponent
does); update #renderCustomComponent to create a local componentInjector (const
componentInjector = this.#getInjector(this.injector())) and pass that
componentInjector to createComponent instead of this.injector(), ensuring the
merged injector with context tokens is used when instantiating the component for
FlexRenderComponentView.
🧹 Nitpick comments (8)
examples/angular/composable-tables/src/app/components/header-components.ts (2)

1-24: Remove commented-out code before merging.

These blocks contain dead code: React-style JSX (lines 1-10) and a commented-out Angular component (lines 15-24). If SortIndicator is planned for future implementation, consider tracking it via an issue instead of leaving commented code in the codebase.


26-36: Consider using a more specific component selector.

Using selector: 'span' matches any <span> element in the DOM. While the component is rendered dynamically via flexRenderComponent (which mitigates accidental matching), a specific selector like 'app-footer-column-id' or an attribute selector '[tanstackFooterColumnId]' would be more explicit and follow Angular best practices.

💡 Suggested selector change
 @Component({
-  selector: 'span',
+  selector: 'app-footer-column-id',
   host: {
     'tanstack-footer-column-id': '',
     class: 'footer-column-id',
   },
   template: `{{ header().column.id }}`,
 })
examples/angular/composable-tables/src/index.html (1)

9-9: Pin the Twind version for reproducibility.

Loading twind/shim without a version specifier means this example could break if Twind releases incompatible changes. For example projects that users may reference later, pinning to a specific version improves reliability.

💡 Suggested improvement
-    <script type="module" src="https://cdn.skypack.dev/twind/shim"></script>
+    <script type="module" src="https://cdn.skypack.dev/[email protected]/shim"></script>
examples/angular/basic-app-table/.devcontainer/devcontainer.json (1)

1-4: Update to a current Node.js LTS version.

Node.js 18 reached end-of-life on April 30, 2025. Update to Node.js 20 or 22 for continued security updates and support.

♻️ Suggested update
 {
   "name": "Node.js",
-  "image": "mcr.microsoft.com/devcontainers/javascript-node:18"
+  "image": "mcr.microsoft.com/devcontainers/javascript-node:20"
 }
examples/angular/composable-tables/src/app/components/cell-components.ts (1)

69-89: Consider typing the row data for better type safety.

The generic parameter <number> in injectTableCellContext<number>() only types the cell value. The access to row.original.firstName and row.original.lastName is not type-checked. For production code, consider defining a proper row data type.

Example with typed row data
// Define a type for your row data
interface Person {
  firstName: string
  lastName: string
  // ... other fields
}

// Then use it in the component (if the API supports it)
// Note: This depends on how injectTableCellContext is typed
examples/angular/basic-app-table/src/app/app.component.html (1)

6-11: Consider renaming the template variable to avoid shadowing.

The let header declaration shadows the outer loop variable header. While this works correctly (the inner scope takes precedence), using a distinct name like let content or let headerContent would improve readability.

This same pattern appears for cell (line 20) and footer (line 31).

Example for header
-            <th *flexRenderHeader="header; let header">
-              <div [innerHTML]="header"></div>
+            <th *flexRenderHeader="header; let content">
+              <div [innerHTML]="content"></div>
             </th>
packages/angular-table/src/flexRender.ts (2)

86-96: Consider whether linkedSignal is the appropriate primitive here.

linkedSignal creates a writable signal that resets when its source changes. If the intent is simply to track the input values reactively without allowing external writes, computed() would be more appropriate and semantically clearer.

The current implementation allows calling .set() on content, props, and injector, which could lead to subtle bugs if they're mutated independently of their inputs.

If writability is intentional for some use case, consider documenting why.


159-171: Consider renaming doCheck to avoid confusion with Angular's ngDoCheck.

The method name doCheck might be mistaken for Angular's lifecycle hook. A name like checkAndUpdate or handleContentChange would be clearer.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6fb4bac and bc95e00.

⛔ Files ignored due to path filters (2)
  • examples/angular/basic-app-table/src/favicon.ico is excluded by !**/*.ico
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • examples/angular/basic-app-table/.devcontainer/devcontainer.json
  • examples/angular/basic-app-table/.editorconfig
  • examples/angular/basic-app-table/.gitignore
  • examples/angular/basic-app-table/README.md
  • examples/angular/basic-app-table/angular.json
  • examples/angular/basic-app-table/package.json
  • examples/angular/basic-app-table/src/app/app.component.html
  • examples/angular/basic-app-table/src/app/app.component.ts
  • examples/angular/basic-app-table/src/app/app.config.ts
  • examples/angular/basic-app-table/src/assets/.gitkeep
  • examples/angular/basic-app-table/src/index.html
  • examples/angular/basic-app-table/src/main.ts
  • examples/angular/basic-app-table/src/styles.scss
  • examples/angular/basic-app-table/tsconfig.app.json
  • examples/angular/basic-app-table/tsconfig.json
  • examples/angular/basic-app-table/tsconfig.spec.json
  • examples/angular/basic/src/app/app.component.ts
  • examples/angular/composable-tables/src/app/components/cell-components.ts
  • examples/angular/composable-tables/src/app/components/header-components.ts
  • examples/angular/composable-tables/src/index.html
  • packages/angular-table/src/flexRender.ts
  • packages/angular-table/src/helpers/table.ts
✅ Files skipped from review due to trivial changes (4)
  • examples/angular/basic-app-table/.gitignore
  • examples/angular/basic-app-table/tsconfig.app.json
  • examples/angular/basic-app-table/README.md
  • examples/angular/basic-app-table/angular.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/angular-table/src/helpers/table.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-15T15:05:14.117Z
Learnt from: stevenwjy
Repo: TanStack/table PR: 6094
File: packages/table-core/src/utils/getGroupedRowModel.ts:74-78
Timestamp: 2025-09-15T15:05:14.117Z
Learning: In TanStack Table's Row interface, the `subRows` property is typed as `Row<TData>[]` (not optional) and the `createRow` function always initializes it as an empty array, so `subRows` is guaranteed to never be undefined.

Applied to files:

  • packages/angular-table/src/flexRender.ts
📚 Learning: 2025-09-15T15:05:14.117Z
Learnt from: stevenwjy
Repo: TanStack/table PR: 6094
File: packages/table-core/src/utils/getGroupedRowModel.ts:74-78
Timestamp: 2025-09-15T15:05:14.117Z
Learning: In TanStack Table's Row interface, the `subRows` property is typed as `Row<TData>[]` (not optional) and the `createRow` function always initializes it as an empty array using `subRows ?? []`, so `subRows` is guaranteed to never be undefined.

Applied to files:

  • packages/angular-table/src/flexRender.ts
🧬 Code graph analysis (4)
examples/angular/composable-tables/src/app/components/header-components.ts (2)
examples/angular/composable-tables/src/app/app.component.ts (1)
  • Component (17-80)
packages/angular-table/src/helpers/header.ts (1)
  • injectTableHeaderContext (37-43)
examples/angular/basic-app-table/src/main.ts (1)
examples/angular/basic-app-table/src/app/app.config.ts (1)
  • appConfig (3-5)
examples/angular/basic-app-table/src/app/app.component.ts (2)
examples/angular/grouping/src/app/columns.ts (1)
  • columns (36-84)
examples/angular/basic/src/app/app.component.ts (1)
  • Component (86-107)
examples/angular/composable-tables/src/app/components/cell-components.ts (4)
packages/angular-table/src/flexRender.ts (1)
  • injectFlexRenderContext (44-44)
packages/table-core/src/core/cells/coreCellsFeature.types.ts (1)
  • CellContext (8-19)
packages/table-core/src/types/TableFeatures.ts (1)
  • TableFeatures (42-43)
packages/angular-table/src/helpers/cell.ts (1)
  • injectTableCellContext (37-43)
🔇 Additional comments (29)
examples/angular/basic-app-table/.editorconfig (1)

1-16: LGTM!

The EditorConfig settings are standard and appropriate for an Angular project. The configuration correctly defines UTF-8 encoding, 2-space indentation (Angular convention), single quotes for TypeScript, and proper Markdown handling.

examples/angular/basic-app-table/tsconfig.json (1)

1-31: Configuration looks good for Angular project.

The TypeScript and Angular compiler options are well-configured with strict type checking enabled. The useDefineForClassFields: false setting is correctly paired with experimentalDecorators: true to ensure proper decorator behavior in Angular.

examples/angular/basic-app-table/tsconfig.spec.json (1)

1-9: LGTM!

Standard Angular test configuration that correctly extends the base config and includes Jasmine types for testing.

examples/angular/basic/src/app/app.component.ts (3)

45-50: Well-structured documentation comments.

The step-by-step comments (steps 3-4) clearly explain the V9 features and provide helpful guidance about tableFeatures and type-safe column definition alternatives. This improves the example's educational value.


95-102: Verify intentional removal of debugTable option.

The debugTable: true option was removed and replaced with a placeholder comment. While this simplifies the example, having debug output enabled in a "basic" example could be helpful for developers learning the library, as it exposes table state in the console.

Was this removal intentional, or should the debug option be retained for educational purposes?


92-107: Component implementation looks good.

The component correctly uses:

  • Signal-based state management for reactive data
  • OnPush change detection for performance
  • The new injectTable API with proper feature and row model configuration

The implementation aligns well with modern Angular patterns.

examples/angular/composable-tables/src/app/components/header-components.ts (1)

38-58: LGTM!

The computed signal chain is well-structured: header → table → rows → sum. The reduce logic correctly handles non-numeric values by treating them as 0, and the template appropriately displays a dash for non-positive sums.

Same selector concern applies here as noted for FooterColumnId.

examples/angular/composable-tables/src/index.html (2)

1-8: LGTM!

Standard HTML5 boilerplate with proper charset, viewport meta tag, and base href configuration for an Angular SPA.


11-13: LGTM!

Clean body structure with the standard Angular bootstrap element.

examples/angular/composable-tables/src/app/components/cell-components.ts (1)

7-17: LGTM on the cell rendering pattern.

The use of injectFlexRenderContext for TextCell/NumberCell and injectTableCellContext (signal-based) for the other components is consistent with the library's API design. The host attribute bindings for CSS class injection in StatusCell and CategoryCell are a clean approach.

Also applies to: 19-29, 31-44

examples/angular/basic-app-table/package.json (2)

1-12: LGTM!

Package metadata is well-configured for an example project with appropriate naming, initial version, and private: true to prevent accidental publishing.


32-32: TypeScript 5.9.3 is compatible with Angular 21 (which requires TypeScript >= 5.9.0 and < 6.0.0). The exact version pinning ensures reproducible builds, which is a standard practice.

examples/angular/basic-app-table/src/app/app.config.ts (1)

1-5: LGTM!

The minimal application configuration with an empty providers array is appropriate for this basic example that doesn't require routing or additional services.

Note: The AI summary mentions provideRouter and routes imports, but the actual implementation has an empty providers array. The code is correct as-is for this simple example.

examples/angular/basic-app-table/src/styles.scss (1)

1-32: LGTM!

Clean and minimal styling appropriate for the example application. The table styles are consistent and the pagination layout is well-structured.

examples/angular/basic-app-table/src/app/app.component.html (1)

1-42: LGTM overall!

The template correctly demonstrates the new composable table directives (*flexRenderHeader, *flexRenderCell, *flexRenderFooter) with the modern Angular control flow syntax. The structure is clean and follows the patterns established in the PR.

examples/angular/basic-app-table/src/main.ts (1)

1-5: LGTM!

Standard Angular standalone application bootstrap with proper error handling. This correctly initializes the application using the modern bootstrapApplication API.

examples/angular/basic-app-table/src/index.html (2)

1-14: LGTM otherwise!

The HTML structure is correct with proper meta tags, viewport configuration, and the Angular app root element.


9-9: > Likely an incorrect or invalid review comment.

examples/angular/basic-app-table/src/app/app.component.ts (2)

72-77: Verify HTML string rendering behavior.

The cell and header functions return HTML strings (e.g., <i>${info.getValue()}</i>). In Angular with FlexRender, raw HTML strings are typically rendered as text content, not parsed HTML. This would display literal <i>value</i> instead of italicized text.

If HTML rendering is intended, consider using Angular components or templates with [innerHTML] binding. If this is intentional for demonstration purposes, a brief comment clarifying the expected output would help.


99-114: Well-structured component demonstrating the new composable table pattern.

The component follows Angular best practices with ChangeDetectionStrategy.OnPush, uses signals for reactive data, and correctly integrates the new injectAppTable hook. The pattern of defining features/row models centrally via createTableHook and then using the returned helpers throughout the application is clean and promotes consistency.

packages/angular-table/src/flexRender.ts (9)

58-63: LGTM!

The new FlexRenderInputContent type correctly represents the allowed input values including the function form that can return dynamic content.


119-157: LGTM! Clean signal-driven lifecycle management.

The constructor properly sets up:

  • Cleanup via DestroyRef.onDestroy for effect and view disposal
  • An effect that tracks props() and content() changes with appropriate flags
  • First-render flag management

The use of untracked(() => this.update()) correctly prevents the update call from being tracked as a dependency.


173-191: LGTM!

The update method correctly prioritizes render flags: full render for content changes, props update for reference changes, and dirty check for signal-driven updates.


247-261: LGTM!

The method correctly dispatches to the appropriate renderer based on content kind.


263-278: LGTM!

The getter pattern for $implicit ensures the template always receives the latest context value reactively.


280-294: LGTM!

The template rendering correctly injects the custom injector for context token availability.


296-309: LGTM!

Correctly merges the FlexRenderComponent's injector with the context injector.


325-358: LGTM! Reactive injector pattern with dynamic providers.

The proxy pattern correctly provides reactive access to context properties. The useValue: () => proxy.table function form ensures consumers get the latest value when accessed.

Note: The 'in' checks (lines 332, 338, 344) evaluate against the current props snapshot. Since a new injector is created on each render cycle, this correctly reflects the current props structure.


14-14: Angular version compatibility for linkedSignal is appropriate. The package requires Angular ≥19, where linkedSignal was introduced as a developer preview and is now stable since Angular v20. The peer dependency is correctly configured.

@riccardoperra
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
packages/angular-table/src/flexRender.ts (1)

239-245: Incorrect bitwise operation in #shouldRecreateEntireView.

The condition uses bitwise AND (&) across all three values, which will only be truthy when all flags are simultaneously set:

this.renderFlags &
FlexRenderFlags.ContentChanged &
FlexRenderFlags.ViewFirstRender

Based on the method name and usage context (determining when to destroy the current effect and recreate the view), the logic should check if either ContentChanged or ViewFirstRender is set. However, reviewing the call site at Line 199, this is invoked to decide whether to destroy the current effect—which should happen on content change but NOT on first render (since there's no prior effect to destroy).

Please verify the intended logic. If the goal is to recreate the view when content changes (but not necessarily on first render), consider:

Possible fix
 #shouldRecreateEntireView() {
   return (
-    this.renderFlags &
-    FlexRenderFlags.ContentChanged &
-    FlexRenderFlags.ViewFirstRender
+    (this.renderFlags & FlexRenderFlags.ContentChanged) !== 0 &&
+    (this.renderFlags & FlexRenderFlags.ViewFirstRender) === 0
   )
 }

Or if the intent is to check either flag:

 #shouldRecreateEntireView() {
   return (
-    this.renderFlags &
-    FlexRenderFlags.ContentChanged &
-    FlexRenderFlags.ViewFirstRender
+    this.renderFlags &
+    (FlexRenderFlags.ContentChanged | FlexRenderFlags.ViewFirstRender)
   )
 }
🤖 Fix all issues with AI agents
In @examples/angular/composable-tables/src/app/app.component.html:
- Around line 2-5: The description references a non-existent hook name
"useAppTable"; update the copy to reference the actual exported hook
"injectAppTable" used across the example (replace "useAppTable" with
"injectAppTable" in the paragraph in app.component.html) so the docs match the
implementation and avoid confusion.

In @examples/angular/composable-tables/src/app/components/table-components.ts:
- Around line 109-113: The onPageChange handler can produce a negative index
when parsing user input (e.g., "0"); update the onPageChange method to parse the
input into a numeric page, then clamp it to a valid range before calling
this.table().setPageIndex(page). Use the table API to determine the max index
(e.g., this.table().getPageCount() - 1 or this.table().getPageOptions().length -
1) and ensure the final page is Math.max(0, Math.min(parsedPage, maxIndex)) so
you never call setPageIndex with a negative or out-of-range value.
- Around line 30-32: The constructor currently calls
this.table().resetColumnFilters(), causing filters to be cleared on every
instantiation; remove that call from the constructor and either delete it
entirely or move it to a lifecycle hook (e.g., implement ngOnInit on the toolbar
component and call this.table().resetColumnFilters() there) with a clear comment
like "Intentionally reset filters on initial toolbar init" if the reset-on-init
behavior is desired; ensure you remove the constructor side-effect or replace it
with the explicit ngOnInit implementation referencing the same
this.table().resetColumnFilters() call.

In @examples/angular/composable-tables/src/app/table.ts:
- Around line 42-52: Remove the leftover commented-out blocks in table.ts:
delete the duplicate import comment block for table-level components (the
commented import lines referencing PaginationControls, RowCount, TableToolbar),
the empty comment placeholders for cell/header/footer components, and the
React-style hook name comments (e.g., references to
useHeaderContext/useFooterContext) so the file contains only active, relevant
Angular code; locate these comments in the area around the existing import
section and the header/footer comment block and remove them prior to merging.
- Line 88: The default getRowId implementation (getRowId: (row) => row.id)
assumes every row has an id and causes runtime errors for UsersTable because
Person lacks id; fix by either adding an id to the Person type, or overriding
getRowId in UsersTable to return a stable fallback (e.g., use the row index or a
unique Person field), or replace the default getRowId with a safer function that
checks for row.id and falls back to an index/other key; update the UsersTable
component (or the shared table config where getRowId is defined) to use the
chosen approach so ProductsTable can keep its override and UsersTable no longer
throws.

In @packages/angular-table/src/helpers/createTableHook.ts:
- Around line 367-373: Change the types for appHeader and appFooter in
createTableHook.ts from Cell to Header: update both the parameter types and
return casts to use Header<TFeatures, TData, any> & TCellComponents (instead of
Cell<...>), and ensure Header is imported from @tanstack/table-core; keep the
function names appHeader and appFooter intact so they match the AppAngularTable
definitions.
🧹 Nitpick comments (10)
examples/angular/composable-tables/src/app/components/products-table/products-table.html (1)

13-14: Variable shadowing may cause confusion.

The template uses _header as the loop variable (Line 13), then creates a new header via @let (Line 14), and then shadows it again with let header in the flexRenderHeader directive (Line 19). While functional, this multi-level shadowing can make the template harder to follow.

Consider using distinct names like rawHeader, appHeader, and headerValue to improve clarity.

Also applies to: 19-21

examples/angular/composable-tables/src/app/components/users-table/users-table.html (1)

1-81: Consider extracting shared table template.

This template is nearly identical to products-table.html, differing only in the toolbar title. Both templates share the same structure for headers, body, footer, pagination, and row count rendering.

Consider extracting a shared table template component that accepts configuration inputs (like title and refresh handler), reducing duplication and maintenance burden. This would align with the DRY principle and the composable design philosophy of this PR.

packages/angular-table/src/flexRender.ts (1)

159-171: Consider renaming doCheck to avoid lifecycle hook confusion.

The private doCheck() method name suggests it implements Angular's DoCheck lifecycle interface, but it's a regular method called from the render effect. Consider renaming to checkAndUpdate() or performDirtyCheck() for clarity.

packages/angular-table/src/helpers/createTableHook.ts (1)

327-333: Type safety lost with any for TData parameter.

Both injectTableHeaderContext and injectTableCellContext use any for the TData type parameter, which loses type safety for the row data type. Consider making TData a generic parameter of these functions or documenting why any is intentionally used here.

Proposed improvement
-  function injectTableHeaderContext<TValue extends CellData = CellData>() {
-    return _injectTableHeaderContext<TFeatures, any, TValue>()
+  function injectTableHeaderContext<
+    TData extends RowData = RowData,
+    TValue extends CellData = CellData,
+  >() {
+    return _injectTableHeaderContext<TFeatures, TData, TValue>()
   }

-  function injectTableCellContext<TValue extends CellData = CellData>() {
-    return _injectTableCellContext<TFeatures, any, TValue>()
+  function injectTableCellContext<
+    TData extends RowData = RowData,
+    TValue extends CellData = CellData,
+  >() {
+    return _injectTableCellContext<TFeatures, TData, TValue>()
   }
examples/angular/composable-tables/src/app/components/users-table/users-table.ts (1)

29-29: Consider documenting the large initial dataset.

Initializing with 5000 rows is intentional for demonstrating pagination, but a comment explaining this would help future maintainers understand it's a deliberate choice for the example, not a production pattern.

-  readonly data = signal(makeData(5000))
+  // Initialize with large dataset to demonstrate pagination and performance
+  readonly data = signal(makeData(5000))
examples/angular/composable-tables/src/app/components/header-components.ts (3)

1-10: Remove commented-out code.

This commented-out React-style reference implementation should be removed to keep the codebase clean. If needed for documentation, consider moving it to a separate doc or README.


52-62: Generic span selector may cause unintended style/behavior conflicts.

Using selector: 'span' is overly generic and could match unintended elements. Consider using a more specific selector like 'footer-column-id' or '[tanstack-footer-column-id]'.

♻️ Suggested change
 @Component({
-  selector: 'span',
+  selector: 'footer-column-id',
   host: {
     'tanstack-footer-column-id': '',
     class: 'footer-column-id',
   },
   template: `{{ header().column.id }}`,
 })

64-84: Same generic span selector issue; also consider edge case for zero sums.

  1. Same selector concern as FooterColumnId - use a more specific selector.

  2. The condition sum() > 0 will display '—' when the sum is exactly zero, which may be a valid computed value (e.g., all values are 0 or positive/negative values cancel out). Consider using a different check if zero should be displayed.

♻️ Suggested changes
 @Component({
-  selector: 'span',
+  selector: 'footer-sum',
   host: {
     'tanstack-footer-sum': '',
     class: 'footer-sum',
   },
-  template: `{{ sum() > 0 ? sum().toLocaleString() : '—' }}`,
+  template: `{{ sum().toLocaleString() }}`,
 })

Or if you want to distinguish "no numeric values" from "sum is zero":

readonly sum = computed(() => {
  let hasNumericValue = false
  const total = this.rows().reduce((acc, row) => {
    const value = row.getValue(this.header().column.id)
    if (typeof value === 'number') {
      hasNumericValue = true
      return acc + value
    }
    return acc
  }, 0)
  return hasNumericValue ? total : null
})

Then in template: {{ sum() !== null ? sum()!.toLocaleString() : '—' }}

packages/angular-table/src/injectTable.ts (1)

117-119: Consider making the state property explicitly read-only.

Object.defineProperty without specifying writable defaults to false, which is correct for a read-only property. However, being explicit improves clarity and ensures the intent is preserved during future refactoring.

♻️ Optional explicit property descriptor
     Object.defineProperty(table, 'state', {
       value: injectStore(table.store, selector, { injector }),
+      writable: false,
+      configurable: false,
     })
examples/angular/composable-tables/src/app/components/table-components.ts (1)

103-107: Inconsistent number formatting between pageIndex and pageCount.

pageCount uses toLocaleString() but pageIndex does not. For consistency, either format both or neither. Since page counts can be large, the current approach for pageCount is reasonable, but the display {{ pageIndex() + 1 }} of {{ pageCount() }} will look inconsistent for large datasets (e.g., "1 of 1,000").

♻️ Optional: consistent formatting
-  readonly pageCount = computed(() =>
-    this.table().getPageCount().toLocaleString(),
-  )
+  readonly pageCount = computed(() => this.table().getPageCount())

Then in template: {{ (pageIndex() + 1).toLocaleString() }} of {{ pageCount().toLocaleString() }}

Or keep both unformatted for simplicity since page counts typically don't need locale formatting.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc95e00 and 9d782a8.

📒 Files selected for processing (14)
  • examples/angular/composable-tables/src/app/app.component.html
  • examples/angular/composable-tables/src/app/app.component.ts
  • examples/angular/composable-tables/src/app/components/header-components.ts
  • examples/angular/composable-tables/src/app/components/products-table/products-table.html
  • examples/angular/composable-tables/src/app/components/products-table/products-table.ts
  • examples/angular/composable-tables/src/app/components/table-components.ts
  • examples/angular/composable-tables/src/app/components/users-table/users-table.html
  • examples/angular/composable-tables/src/app/components/users-table/users-table.ts
  • examples/angular/composable-tables/src/app/table.ts
  • examples/angular/composable-tables/src/index.html
  • packages/angular-table/src/flexRender.ts
  • packages/angular-table/src/helpers/createTableHook.ts
  • packages/angular-table/src/helpers/table.ts
  • packages/angular-table/src/injectTable.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/angular/composable-tables/src/app/app.component.ts
  • examples/angular/composable-tables/src/index.html
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-15T15:05:14.117Z
Learnt from: stevenwjy
Repo: TanStack/table PR: 6094
File: packages/table-core/src/utils/getGroupedRowModel.ts:74-78
Timestamp: 2025-09-15T15:05:14.117Z
Learning: In TanStack Table's Row interface, the `subRows` property is typed as `Row<TData>[]` (not optional) and the `createRow` function always initializes it as an empty array using `subRows ?? []`, so `subRows` is guaranteed to never be undefined.

Applied to files:

  • examples/angular/composable-tables/src/app/table.ts
  • packages/angular-table/src/helpers/table.ts
  • packages/angular-table/src/flexRender.ts
📚 Learning: 2025-09-15T15:05:14.117Z
Learnt from: stevenwjy
Repo: TanStack/table PR: 6094
File: packages/table-core/src/utils/getGroupedRowModel.ts:74-78
Timestamp: 2025-09-15T15:05:14.117Z
Learning: In TanStack Table's Row interface, the `subRows` property is typed as `Row<TData>[]` (not optional) and the `createRow` function always initializes it as an empty array, so `subRows` is guaranteed to never be undefined.

Applied to files:

  • examples/angular/composable-tables/src/app/table.ts
  • packages/angular-table/src/helpers/table.ts
  • packages/angular-table/src/flexRender.ts
🧬 Code graph analysis (5)
examples/angular/composable-tables/src/app/components/header-components.ts (3)
packages/angular-table/src/helpers/header.ts (1)
  • injectTableHeaderContext (37-43)
packages/angular-table/src/flex-render/flex-render-component.ts (1)
  • FlexRenderComponent (94-119)
packages/angular-table/src/flexRender.ts (1)
  • flexRenderComponent (296-309)
examples/angular/composable-tables/src/app/components/users-table/users-table.ts (1)
examples/angular/composable-tables/src/app/components/header-components.ts (3)
  • Component (33-50)
  • Component (52-62)
  • Component (64-84)
examples/angular/composable-tables/src/app/components/products-table/products-table.ts (1)
examples/angular/composable-tables/src/app/makeData.ts (2)
  • Product (13-20)
  • makeProductData (75-77)
packages/angular-table/src/helpers/createTableHook.ts (8)
packages/angular-table/src/flexRender.ts (1)
  • FlexRenderContent (48-56)
packages/table-core/src/core/cells/coreCellsFeature.types.ts (1)
  • CellContext (8-19)
packages/table-core/src/types/Header.ts (1)
  • Header (23-37)
packages/angular-table/src/injectTable.ts (2)
  • AngularTable (22-38)
  • injectTable (40-123)
packages/table-core/src/types/TableOptions.ts (1)
  • TableOptions (51-98)
packages/angular-table/src/helpers/table.ts (1)
  • injectTableContext (30-36)
packages/angular-table/src/helpers/header.ts (1)
  • injectTableHeaderContext (37-43)
packages/angular-table/src/helpers/cell.ts (1)
  • injectTableCellContext (37-43)
examples/angular/composable-tables/src/app/components/table-components.ts (1)
packages/angular-table/src/helpers/table.ts (1)
  • injectTableContext (30-36)
🔇 Additional comments (14)
examples/angular/composable-tables/src/app/components/products-table/products-table.html (2)

1-81: LGTM overall structure.

The template correctly integrates TanStack table directives with Angular's control flow syntax (@for, @if, @let). The use of track expressions for performance optimization and the composition of toolbar, pagination, and row count components demonstrate good practices.


29-31: No XSS risk. SortIndicator returns a hardcoded HTML string containing only static emoji characters (🔼/🔽) based on sort state—no user input is interpolated. ColumnFilter returns a component reference (FlexRenderComponent), not HTML, so the flexRender directive handles rendering safely.

Likely an incorrect or invalid review comment.

examples/angular/composable-tables/src/app/components/users-table/users-table.html (1)

29-31: Same innerHTML concern applies here.

As noted in the products-table review, the [innerHTML] bindings for SortIndicator and ColumnFilter should be verified for safe content.

Also applies to: 39-41

packages/angular-table/src/flexRender.ts (2)

119-156: Effect-based change tracking is well-structured.

The constructor effect properly tracks props() and content() signals, sets appropriate flags for changes, and triggers updates via untracked(). The cleanup logic in DestroyRef.onDestroy correctly handles both the effect reference and render view.


325-358: The in operator checks the stale Proxy target, not through the get trap.

The Proxy is created with this.props() as the target (evaluated once at line 327), but the get trap dynamically calls getContext() to access the current props. However, the in operator (lines 332, 338, 344) checks property presence directly against the stale target without invoking the get trap. This means the presence checks ('table' in proxy, etc.) are evaluated against the props snapshot captured at Proxy creation time, not the current props.

Since each #getInjector() call is made fresh during rendering and receives the latest props reference at that moment, this is more of a synchronous quirk within each invocation rather than a cumulative staleness issue. The registered provider callbacks remain reactive via the get trap. Still worth noting for maintainability—if the logic around provider registration ever changes, this distinction could become problematic.

packages/angular-table/src/helpers/createTableHook.ts (1)

243-263: Well-designed AppAngularTable type with helper methods.

The AppAngularTable type cleanly extends AngularTable and adds typed helper methods (appCell, appHeader, appFooter) that return the base types intersected with the registered components. The use of NoInfer prevents unwanted type inference widening.

examples/angular/composable-tables/src/app/app.component.html (1)

7-11: LGTM - Clean component composition.

The template demonstrates the composable pattern well by rendering two different table types side-by-side with a divider.

examples/angular/composable-tables/src/app/components/users-table/users-table.ts (2)

31-67: Well-structured column definitions.

The column definitions cleanly demonstrate the composable pattern:

  • Accessor columns use appropriate cell components (TextCell, NumberCell, StatusCell, ProgressCell)
  • Footer components (FooterColumnId, FooterSum) are appropriately matched to column types
  • The display column for actions shows how to add non-data columns

69-74: Table options function is reactive.

Using this.data() inside the options factory function ensures the table reacts to data changes. The debugTable: true flag is appropriate for an example but should typically be disabled in production.

examples/angular/composable-tables/src/app/table.ts (1)

62-115: Well-organized hook configuration.

The createTableHook call cleanly separates concerns:

  • Features and row models are centrally defined
  • Components are categorized by scope (table, cell, header)
  • Default options like getRowId are shared across all tables

This pattern enables consistent table behavior across the application while maintaining flexibility for individual table configurations.

examples/angular/composable-tables/src/app/components/products-table/products-table.ts (1)

1-68: LGTM! Well-structured component following established patterns.

The component follows the composable table pattern correctly with signal-based data, OnPush change detection, and proper column helper usage. The onRefresh method correctly creates a new array reference to trigger reactivity.

Minor note: NgComponentOutlet is imported but may not be used directly in this component's template (assuming it's used in the HTML template file).

packages/angular-table/src/helpers/table.ts (1)

1-36: LGTM! Clean DI pattern for table context.

The implementation correctly uses Angular's dependency injection to provide table context to child components:

  • TanStackTableToken properly defines the injection token
  • The directive's provider factory correctly exposes the input signal
  • injectTableContext() provides a clean API for consumers

The use of any in the token type is acceptable since the actual type is parameterized at the injection site.

packages/angular-table/src/injectTable.ts (1)

20-47: LGTM! API improvements for signal-based state subscriptions.

The changes improve the API:

  • Adding ValueEqualityFn support enables custom equality checks for fine-grained reactivity control
  • Defaulting TSelected to TableState<TFeatures> provides better ergonomics for the common case
  • The selector default aligns with the new type default
examples/angular/composable-tables/src/app/components/table-components.ts (1)

42-52: LGTM! Clean row count component.

The RowCount component correctly uses computed signals and toLocaleString() for user-friendly number formatting.

@riccardoperra
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 1

🤖 Fix all issues with AI agents
In @examples/angular/composable-tables/src/app/table.ts:
- Around line 52-64: Fix the doc typo and sync the exports with the
documentation: change the documentation line referencing
injectFlexRenderCellContext to say "cell-level typings" instead of "header-level
typings", and either remove the two doc bullets for
injectFlexRenderHeaderContext/injectFlexRenderCellContext or uncomment the
corresponding exports (injectFlexRenderHeaderContext and
injectFlexRenderCellContext) in the exported destructuring from createTableHook
so the public API matches the docs.
🧹 Nitpick comments (2)
packages/angular-table/src/helpers/createTableHook.ts (2)

52-60: Consider clarifying the FlexRender property or removing if unused.

The FlexRender: () => unknown property on the enhanced cell type appears undocumented and its purpose is unclear. If this is a placeholder for future functionality, consider adding a doc comment. If it's unused, consider removing it to keep the API surface clean.


445-453: Loose type cast obscures type safety.

The cast to AngularTable<any, any> at line 452 discards the precise generic typing established throughout this module. While this may be necessary due to TypeScript limitations with complex mapped types, it would be safer to use the exact return type or add a comment explaining why the cast is required.

💡 Suggestion

Consider adding a comment explaining the cast, or attempting a more precise cast:

     return injectTable<TFeatures, TData, TSelected>(() => {
       const options = {
         ...defaultTableOptions,
         ...tableOptions(),
       } as TableOptions<TFeatures, TData>
       options._features = { ...options._features, appTableFeatures }
       return options
-    }, selector) as AngularTable<any, any>
+    }, selector) as AppAngularTable<
+      TFeatures,
+      TData,
+      TSelected,
+      TTableComponents,
+      TCellComponents,
+      THeaderComponents
+    >
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d782a8 and 43014d5.

📒 Files selected for processing (4)
  • examples/angular/basic/src/app/app.component.ts
  • examples/angular/composable-tables/src/app/app.component.html
  • examples/angular/composable-tables/src/app/table.ts
  • packages/angular-table/src/helpers/createTableHook.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/angular/composable-tables/src/app/app.component.html
  • examples/angular/basic/src/app/app.component.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-15T15:05:14.117Z
Learnt from: stevenwjy
Repo: TanStack/table PR: 6094
File: packages/table-core/src/utils/getGroupedRowModel.ts:74-78
Timestamp: 2025-09-15T15:05:14.117Z
Learning: In TanStack Table's Row interface, the `subRows` property is typed as `Row<TData>[]` (not optional) and the `createRow` function always initializes it as an empty array using `subRows ?? []`, so `subRows` is guaranteed to never be undefined.

Applied to files:

  • examples/angular/composable-tables/src/app/table.ts
📚 Learning: 2025-09-15T15:05:14.117Z
Learnt from: stevenwjy
Repo: TanStack/table PR: 6094
File: packages/table-core/src/utils/getGroupedRowModel.ts:74-78
Timestamp: 2025-09-15T15:05:14.117Z
Learning: In TanStack Table's Row interface, the `subRows` property is typed as `Row<TData>[]` (not optional) and the `createRow` function always initializes it as an empty array, so `subRows` is guaranteed to never be undefined.

Applied to files:

  • examples/angular/composable-tables/src/app/table.ts
🧬 Code graph analysis (1)
packages/angular-table/src/helpers/createTableHook.ts (9)
packages/table-core/src/types/Cell.ts (1)
  • Cell (22-33)
packages/table-core/src/core/cells/coreCellsFeature.types.ts (1)
  • CellContext (8-19)
packages/table-core/src/types/Header.ts (1)
  • Header (23-37)
packages/table-core/src/types/TableOptions.ts (1)
  • TableOptions (51-98)
packages/table-core/src/core/headers/coreHeadersFeature.types.ts (1)
  • HeaderContext (30-47)
packages/table-core/src/types/TableState.ts (1)
  • TableState (23-65)
packages/angular-table/src/helpers/table.ts (1)
  • injectTableContext (30-36)
packages/angular-table/src/helpers/header.ts (1)
  • injectTableHeaderContext (37-43)
packages/angular-table/src/helpers/cell.ts (1)
  • injectTableCellContext (37-43)
🔇 Additional comments (6)
examples/angular/composable-tables/src/app/table.ts (1)

65-107: LGTM!

The hook configuration cleanly centralizes features, row models, default options, and component registrations. This pattern enables consistent table setups across the application.

packages/angular-table/src/helpers/createTableHook.ts (5)

168-238: LGTM!

The AppColumnHelper type comprehensively mirrors the core column helper while injecting enhanced context types. The generics flow correctly from features through to cell/header contexts.


240-263: LGTM!

Good use of NoInfer on component types to prevent unwanted type inference widening. The helper methods (appCell, appHeader, appFooter) provide a clean API for consumers to access typed cell/header objects.


433-443: LGTM!

The appTableFeatures TableFeature elegantly leverages the existing feature hook system (constructTableAPIs, assignCellPrototype, assignHeaderPrototype) to inject components at the appropriate levels. This avoids runtime wrapper overhead while maintaining type safety through the augmented types.


455-469: LGTM!

Clean approach — zero runtime overhead with the type cast providing enhanced context types. The comment clearly explains the rationale.


471-479: LGTM!

The returned object correctly exposes all documented helpers, matching the CreateTableHookResult type contract.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants