diff --git a/examples/angular/row-selection-signal/.devcontainer/devcontainer.json b/examples/angular/row-selection-signal/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000..36f47d8762
--- /dev/null
+++ b/examples/angular/row-selection-signal/.devcontainer/devcontainer.json
@@ -0,0 +1,4 @@
+{
+ "name": "Node.js",
+ "image": "mcr.microsoft.com/devcontainers/javascript-node:18"
+}
diff --git a/examples/angular/row-selection-signal/.editorconfig b/examples/angular/row-selection-signal/.editorconfig
new file mode 100644
index 0000000000..59d9a3a3e7
--- /dev/null
+++ b/examples/angular/row-selection-signal/.editorconfig
@@ -0,0 +1,16 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.ts]
+quote_type = single
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/examples/angular/row-selection-signal/.gitignore b/examples/angular/row-selection-signal/.gitignore
new file mode 100644
index 0000000000..0711527ef9
--- /dev/null
+++ b/examples/angular/row-selection-signal/.gitignore
@@ -0,0 +1,42 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# Compiled output
+/dist
+/tmp
+/out-tsc
+/bazel-out
+
+# Node
+/node_modules
+npm-debug.log
+yarn-error.log
+
+# IDEs and editors
+.idea/
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# Visual Studio Code
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+.history/*
+
+# Miscellaneous
+/.angular/cache
+.sass-cache/
+/connect.lock
+/coverage
+/libpeerconnection.log
+testem.log
+/typings
+
+# System files
+.DS_Store
+Thumbs.db
diff --git a/examples/angular/row-selection-signal/README.md b/examples/angular/row-selection-signal/README.md
new file mode 100644
index 0000000000..73a201f1eb
--- /dev/null
+++ b/examples/angular/row-selection-signal/README.md
@@ -0,0 +1,27 @@
+# Selection
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2.
+
+## Development server
+
+Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
+
+## Running unit tests
+
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
diff --git a/examples/angular/row-selection-signal/angular.json b/examples/angular/row-selection-signal/angular.json
new file mode 100644
index 0000000000..7bae30f0ba
--- /dev/null
+++ b/examples/angular/row-selection-signal/angular.json
@@ -0,0 +1,81 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "selection": {
+ "projectType": "application",
+ "schematics": {
+ "@schematics/angular:component": {
+ "style": "scss"
+ }
+ },
+ "root": "",
+ "sourceRoot": "src",
+ "prefix": "app",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:application",
+ "options": {
+ "outputPath": "dist/selection-signal",
+ "index": "src/index.html",
+ "browser": "src/main.ts",
+ "polyfills": ["zone.js"],
+ "tsConfig": "tsconfig.app.json",
+ "inlineStyleLanguage": "scss",
+ "assets": ["src/favicon.ico", "src/assets"],
+ "styles": ["src/styles.scss"],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "budgets": [],
+ "outputHashing": "all"
+ },
+ "development": {
+ "optimization": false,
+ "extractLicenses": false,
+ "sourceMap": true
+ }
+ },
+ "defaultConfiguration": "production"
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "configurations": {
+ "production": {
+ "buildTarget": "selection:build:production"
+ },
+ "development": {
+ "buildTarget": "selection:build:development"
+ }
+ },
+ "defaultConfiguration": "development"
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "buildTarget": "selection:build"
+ }
+ },
+ "test": {
+ "builder": "@angular-devkit/build-angular:karma",
+ "options": {
+ "polyfills": ["zone.js", "zone.js/testing"],
+ "tsConfig": "tsconfig.spec.json",
+ "inlineStyleLanguage": "scss",
+ "assets": ["src/favicon.ico", "src/assets"],
+ "styles": ["src/styles.scss"],
+ "scripts": []
+ }
+ }
+ }
+ }
+ },
+ "cli": {
+ "analytics": false,
+ "cache": {
+ "enabled": false
+ }
+ }
+}
diff --git a/examples/angular/row-selection-signal/package.json b/examples/angular/row-selection-signal/package.json
new file mode 100644
index 0000000000..1af5fb9567
--- /dev/null
+++ b/examples/angular/row-selection-signal/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "tanstack-table-example-angular-row-selection-signal",
+ "version": "0.0.0",
+ "scripts": {
+ "ng": "ng",
+ "start": "ng serve",
+ "build": "ng build",
+ "watch": "ng build --watch --configuration development",
+ "test": "ng test"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/animations": "^17.3.9",
+ "@angular/common": "^17.3.9",
+ "@angular/compiler": "^17.3.9",
+ "@angular/core": "^17.3.9",
+ "@angular/forms": "^17.3.9",
+ "@angular/platform-browser": "^17.3.9",
+ "@angular/platform-browser-dynamic": "^17.3.9",
+ "@faker-js/faker": "^8.4.1",
+ "@tanstack/angular-table": "^8.18.1",
+ "rxjs": "~7.8.1",
+ "tslib": "^2.6.2",
+ "zone.js": "~0.14.4"
+ },
+ "devDependencies": {
+ "@angular-devkit/build-angular": "^17.3.8",
+ "@angular/cli": "^17.3.8",
+ "@angular/compiler-cli": "^17.3.9",
+ "@types/jasmine": "~5.1.4",
+ "jasmine-core": "~5.1.2",
+ "karma": "~6.4.3",
+ "karma-chrome-launcher": "~3.2.0",
+ "karma-coverage": "~2.2.1",
+ "karma-jasmine": "~5.1.0",
+ "karma-jasmine-html-reporter": "~2.1.0",
+ "typescript": "5.4.5"
+ }
+}
diff --git a/examples/angular/row-selection-signal/src/app/app.component.html b/examples/angular/row-selection-signal/src/app/app.component.html
new file mode 100644
index 0000000000..c2dfb725f7
--- /dev/null
+++ b/examples/angular/row-selection-signal/src/app/app.component.html
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Page
+
+ {{ table.getState().pagination.pageIndex + 1 }} of
+ {{ table.getPageCount() }}
+
+
+
+ | Go to page:
+
+
+
+
+
+
+
+ {{ rowSelectionLength() }} of
+ {{ table.getPreFilteredRowModel().rows.length }} Total Rows
+
+
+
+
+
+
+
+
+
+
+
+
{{ stringifiedRowSelection() }}
+
+
+
+
+ Age 🥳
+
diff --git a/examples/angular/row-selection-signal/src/app/app.component.ts b/examples/angular/row-selection-signal/src/app/app.component.ts
new file mode 100644
index 0000000000..5e2e103b4a
--- /dev/null
+++ b/examples/angular/row-selection-signal/src/app/app.component.ts
@@ -0,0 +1,147 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ computed,
+ signal,
+ TemplateRef,
+ viewChild,
+} from '@angular/core'
+import {
+ ColumnDef,
+ createAngularTable,
+ FlexRenderDirective,
+ getCoreRowModel,
+ getFilteredRowModel,
+ getPaginationRowModel,
+ RowSelectionState,
+} from '@tanstack/angular-table'
+import { FilterComponent } from './filter'
+import { makeData, type Person } from './makeData'
+import {
+ TableHeadSelectionComponent,
+ TableRowSelectionComponent,
+} from './selection-column.component'
+
+@Component({
+ selector: 'app-root',
+ standalone: true,
+ imports: [FilterComponent, FlexRenderDirective],
+ templateUrl: './app.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class AppComponent {
+ private readonly rowSelection = signal({})
+ readonly globalFilter = signal('')
+ readonly data = signal(makeData(10_000))
+
+ readonly ageHeaderCell =
+ viewChild.required>('ageHeaderCell')
+
+ readonly columns: ColumnDef[] = [
+ {
+ id: 'select',
+ header: () => TableHeadSelectionComponent,
+ cell: () => TableRowSelectionComponent,
+ },
+ {
+ header: 'Name',
+ footer: props => props.column.id,
+ columns: [
+ {
+ accessorKey: 'firstName',
+ cell: info => info.getValue(),
+ footer: props => props.column.id,
+ header: 'First name',
+ },
+ {
+ accessorFn: row => row.lastName,
+ id: 'lastName',
+ cell: info => info.getValue(),
+ header: () => 'Last Name',
+ footer: props => props.column.id,
+ },
+ ],
+ },
+ {
+ header: 'Info',
+ footer: props => props.column.id,
+ columns: [
+ {
+ accessorKey: 'age',
+ header: () => this.ageHeaderCell(),
+ footer: props => props.column.id,
+ },
+ {
+ header: 'More Info',
+ columns: [
+ {
+ accessorKey: 'visits',
+ header: () => 'Visits',
+ footer: props => props.column.id,
+ },
+ {
+ accessorKey: 'status',
+ header: 'Status',
+ footer: props => props.column.id,
+ },
+ {
+ accessorKey: 'progress',
+ header: 'Profile Progress',
+ footer: props => props.column.id,
+ },
+ ],
+ },
+ ],
+ },
+ ]
+
+ table = createAngularTable(() => ({
+ data: this.data(),
+ columns: this.columns,
+ state: {
+ rowSelection: this.rowSelection(),
+ },
+ enableRowSelection: true, // enable row selection for all rows
+ // enableRowSelection: row => row.original.age > 18, // or enable row selection conditionally per row
+ onRowSelectionChange: updaterOrValue => {
+ this.rowSelection.set(
+ typeof updaterOrValue === 'function'
+ ? updaterOrValue(this.rowSelection())
+ : updaterOrValue
+ )
+ },
+ getCoreRowModel: getCoreRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ debugTable: true,
+ }))
+
+ readonly stringifiedRowSelection = computed(() =>
+ JSON.stringify(this.rowSelection(), null, 2)
+ )
+
+ readonly rowSelectionLength = computed(
+ () => Object.keys(this.rowSelection()).length
+ )
+
+ onPageInputChange(event: Event): void {
+ const inputElement = event.target as HTMLInputElement
+ const page = inputElement.value ? Number(inputElement.value) - 1 : 0
+ this.table.setPageIndex(page)
+ }
+
+ onPageSizeChange(event: any): void {
+ this.table.setPageSize(Number(event.target.value))
+ }
+
+ logSelectedFlatRows(): void {
+ console.info(
+ 'table.getSelectedRowModel().flatRows',
+ this.table.getSelectedRowModel().flatRows
+ )
+ }
+
+ refreshData(): void {
+ this.data.set(makeData(10_000))
+ }
+}
diff --git a/examples/angular/row-selection-signal/src/app/app.config.ts b/examples/angular/row-selection-signal/src/app/app.config.ts
new file mode 100644
index 0000000000..f27099f33c
--- /dev/null
+++ b/examples/angular/row-selection-signal/src/app/app.config.ts
@@ -0,0 +1,5 @@
+import { ApplicationConfig } from '@angular/core'
+
+export const appConfig: ApplicationConfig = {
+ providers: [],
+}
diff --git a/examples/angular/row-selection-signal/src/app/filter.ts b/examples/angular/row-selection-signal/src/app/filter.ts
new file mode 100644
index 0000000000..cf0b567b36
--- /dev/null
+++ b/examples/angular/row-selection-signal/src/app/filter.ts
@@ -0,0 +1,75 @@
+import { CommonModule } from '@angular/common'
+import { Component, input, OnInit } from '@angular/core'
+import { Column } from '@tanstack/angular-table'
+import type { Table } from '@tanstack/angular-table'
+
+@Component({
+ selector: 'app-table-filter',
+ template: ` @if (columnType) {
+ @if (columnType == 'number') {
+
+
+
+
+ } @else {
+
+ }
+ }`,
+ standalone: true,
+ imports: [CommonModule],
+})
+export class FilterComponent implements OnInit {
+ column = input.required>()
+
+ table = input.required>()
+
+ columnType!: string
+
+ ngOnInit() {
+ this.columnType = typeof this.table()
+ .getPreFilteredRowModel()
+ .flatRows[0]?.getValue(this.column().id)
+ }
+
+ getMinValue() {
+ const minValue = this.column().getFilterValue() as any
+
+ return (minValue?.[0] ?? '') as string
+ }
+
+ getMaxValue() {
+ const maxValue = this.column().getFilterValue() as any
+ return (maxValue?.[1] ?? '') as string
+ }
+
+ updateMinFilterValue(newValue: string): void {
+ this.column().setFilterValue((old: any) => {
+ return [newValue, old?.[1]]
+ })
+ }
+
+ updateMaxFilterValue(newValue: string): void {
+ this.column().setFilterValue((old: any) => [old?.[0], newValue])
+ }
+}
diff --git a/examples/angular/row-selection-signal/src/app/makeData.ts b/examples/angular/row-selection-signal/src/app/makeData.ts
new file mode 100644
index 0000000000..331dd1eb19
--- /dev/null
+++ b/examples/angular/row-selection-signal/src/app/makeData.ts
@@ -0,0 +1,48 @@
+import { faker } from '@faker-js/faker'
+
+export type Person = {
+ firstName: string
+ lastName: string
+ age: number
+ visits: number
+ progress: number
+ status: 'relationship' | 'complicated' | 'single'
+ subRows?: Person[]
+}
+
+const range = (len: number) => {
+ const arr: number[] = []
+ for (let i = 0; i < len; i++) {
+ arr.push(i)
+ }
+ return arr
+}
+
+const newPerson = (): Person => {
+ return {
+ firstName: faker.person.firstName(),
+ lastName: faker.person.lastName(),
+ age: faker.number.int(40),
+ visits: faker.number.int(1000),
+ progress: faker.number.int(100),
+ status: faker.helpers.shuffle([
+ 'relationship',
+ 'complicated',
+ 'single',
+ ])[0]!,
+ }
+}
+
+export function makeData(...lens: number[]) {
+ const makeDataLevel = (depth = 0): Person[] => {
+ const len = lens[depth]!
+ return range(len).map((d): Person => {
+ return {
+ ...newPerson(),
+ subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
+ }
+ })
+ }
+
+ return makeDataLevel()
+}
diff --git a/examples/angular/row-selection-signal/src/app/selection-column.component.ts b/examples/angular/row-selection-signal/src/app/selection-column.component.ts
new file mode 100644
index 0000000000..d6f6045bd9
--- /dev/null
+++ b/examples/angular/row-selection-signal/src/app/selection-column.component.ts
@@ -0,0 +1,47 @@
+import { ChangeDetectionStrategy, Component, input } from '@angular/core'
+import { Row, Table } from '@tanstack/angular-table'
+
+@Component({
+ template: `
+
+ `,
+ host: {
+ class: 'px-1 block',
+ },
+ standalone: true,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class TableHeadSelectionComponent {
+ // Your component should also reflect the fields you use as props in flexRenderer directive.
+ // Define the fields as input you want to use in your component
+ // ie. In this case, you are passing HeaderContext object as props in flexRenderer directive.
+ // You can define and use the table field, which is defined in HeaderContext.
+ // Please take note that only signal based input is supported.
+
+ //column = input.required>()
+ //header = input.required>()
+ table = input.required>()
+}
+
+@Component({
+ template: `
+
+ `,
+ host: {
+ class: 'px-1 block',
+ },
+ standalone: true,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class TableRowSelectionComponent {
+ row = input.required>()
+}
diff --git a/examples/angular/row-selection-signal/src/assets/.gitkeep b/examples/angular/row-selection-signal/src/assets/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/examples/angular/row-selection-signal/src/favicon.ico b/examples/angular/row-selection-signal/src/favicon.ico
new file mode 100644
index 0000000000..57614f9c96
Binary files /dev/null and b/examples/angular/row-selection-signal/src/favicon.ico differ
diff --git a/examples/angular/row-selection-signal/src/index.html b/examples/angular/row-selection-signal/src/index.html
new file mode 100644
index 0000000000..27917d2b28
--- /dev/null
+++ b/examples/angular/row-selection-signal/src/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+ Selection
+
+
+
+
+
+
+
+
+
diff --git a/examples/angular/row-selection-signal/src/main.ts b/examples/angular/row-selection-signal/src/main.ts
new file mode 100644
index 0000000000..0c3b92057c
--- /dev/null
+++ b/examples/angular/row-selection-signal/src/main.ts
@@ -0,0 +1,5 @@
+import { bootstrapApplication } from '@angular/platform-browser'
+import { appConfig } from './app/app.config'
+import { AppComponent } from './app/app.component'
+
+bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err))
diff --git a/examples/angular/row-selection-signal/src/styles.scss b/examples/angular/row-selection-signal/src/styles.scss
new file mode 100644
index 0000000000..43c09e0f6b
--- /dev/null
+++ b/examples/angular/row-selection-signal/src/styles.scss
@@ -0,0 +1,26 @@
+html {
+ font-family: sans-serif;
+ font-size: 14px;
+}
+
+table {
+ border: 1px solid lightgray;
+}
+
+tbody {
+ border-bottom: 1px solid lightgray;
+}
+
+th {
+ border-bottom: 1px solid lightgray;
+ border-right: 1px solid lightgray;
+ padding: 2px 4px;
+}
+
+tfoot {
+ color: gray;
+}
+
+tfoot th {
+ font-weight: normal;
+}
diff --git a/examples/angular/row-selection-signal/tsconfig.app.json b/examples/angular/row-selection-signal/tsconfig.app.json
new file mode 100644
index 0000000000..84f1f992d2
--- /dev/null
+++ b/examples/angular/row-selection-signal/tsconfig.app.json
@@ -0,0 +1,10 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/app",
+ "types": []
+ },
+ "files": ["src/main.ts"],
+ "include": ["src/**/*.d.ts"]
+}
diff --git a/examples/angular/row-selection-signal/tsconfig.json b/examples/angular/row-selection-signal/tsconfig.json
new file mode 100644
index 0000000000..fd2d87ac26
--- /dev/null
+++ b/examples/angular/row-selection-signal/tsconfig.json
@@ -0,0 +1,30 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "outDir": "./dist/out-tsc",
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "sourceMap": true,
+ "declaration": false,
+ "experimentalDecorators": true,
+ "moduleResolution": "node",
+ "importHelpers": true,
+ "target": "ES2022",
+ "module": "ES2022",
+ "useDefineForClassFields": false,
+ "lib": ["ES2022", "dom"]
+ },
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "strictTemplates": true
+ }
+}
diff --git a/examples/angular/row-selection-signal/tsconfig.spec.json b/examples/angular/row-selection-signal/tsconfig.spec.json
new file mode 100644
index 0000000000..47e3dd7551
--- /dev/null
+++ b/examples/angular/row-selection-signal/tsconfig.spec.json
@@ -0,0 +1,9 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/spec",
+ "types": ["jasmine"]
+ },
+ "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
+}
diff --git a/packages/angular-table/src/__tests__/flex-render.test.ts b/packages/angular-table/src/__tests__/flex-render.test.ts
index fa3cd4fdac..6d678c573c 100644
--- a/packages/angular-table/src/__tests__/flex-render.test.ts
+++ b/packages/angular-table/src/__tests__/flex-render.test.ts
@@ -1,22 +1,13 @@
-import { describe, expect, test } from 'vitest'
-import {
- ChangeDetectionStrategy,
- ChangeDetectorRef,
- Component,
- inject,
- Input,
- input,
- type OnInit,
- type TemplateRef,
- ViewChild,
-} from '@angular/core'
+import { Component, ViewChild, input, type TemplateRef } from '@angular/core'
+import { TestBed, type ComponentFixture } from '@angular/core/testing'
import { createColumnHelper } from '@tanstack/table-core'
+import { skip } from 'node:test'
+import { describe, expect, test } from 'vitest'
import {
FlexRenderComponent,
FlexRenderDirective,
injectFlexRenderContext,
} from '../flex-render'
-import { type ComponentFixture, TestBed } from '@angular/core/testing'
import { setFixtureSignalInput, setFixtureSignalInputs } from './test-utils'
interface Data {
@@ -124,6 +115,37 @@ describe('FlexRenderDirective', () => {
expect(fixture.nativeElement.textContent).toEqual('Updated value')
})
+
+ // Skip for now, test framework (using ComponentRef.setInput) cannot recognize signal inputs
+ // as component inputs
+ skip('should render custom components', () => {
+ @Component({
+ template: `{{ row().property }}`,
+ standalone: true,
+ })
+ class FakeComponent {
+ row = input.required<{ property: string }>()
+ }
+
+ const fixture = TestBed.createComponent(TestRenderComponent)
+ setFixtureSignalInputs(fixture, {
+ content: () => FakeComponent,
+ context: {
+ row: {
+ property: 'Row value',
+ },
+ },
+ })
+
+ expect(fixture.nativeElement.textContent).toEqual('Row value')
+
+ setFixtureSignalInput(fixture, 'context', {
+ row: { property: 'Updated value' },
+ })
+ fixture.detectChanges()
+
+ expect(fixture.nativeElement.textContent).toEqual('Updated value')
+ })
})
@Component({
diff --git a/packages/angular-table/src/flex-render.ts b/packages/angular-table/src/flex-render.ts
index ac74a72f83..be1d114dd1 100644
--- a/packages/angular-table/src/flex-render.ts
+++ b/packages/angular-table/src/flex-render.ts
@@ -2,22 +2,24 @@ import {
ChangeDetectorRef,
ComponentRef,
Directive,
- type DoCheck,
EmbeddedViewRef,
Inject,
- inject,
InjectionToken,
Injector,
Input,
- type OnInit,
TemplateRef,
- type Type,
+ Type,
ViewContainerRef,
+ inject,
+ isSignal,
+ type DoCheck,
+ type OnInit,
} from '@angular/core'
export type FlexRenderContent> =
| string
| number
+ | Type
| FlexRenderComponent
| TemplateRef<{ $implicit: TProps }>
| null
@@ -91,6 +93,8 @@ export class FlexRenderDirective>
)
} else if (content instanceof FlexRenderComponent) {
return this.renderComponent(content)
+ } else if (content instanceof Type) {
+ return this.renderCustomComponent(content)
} else {
return null
}
@@ -137,6 +141,25 @@ export class FlexRenderDirective>
return componentRef
}
+ private renderCustomComponent(
+ component: Type
+ ): ComponentRef {
+ const componentRef = this.viewContainerRef.createComponent(component, {
+ injector: this.injector,
+ })
+ for (const prop in this.props) {
+ // Only signal based input can be added here
+ if (
+ componentRef.instance?.hasOwnProperty(prop) &&
+ // @ts-ignore
+ isSignal(componentRef.instance[prop])
+ ) {
+ componentRef.setInput(prop, this.props[prop])
+ }
+ }
+ return componentRef
+ }
+
private getTemplateRefContext() {
const getContext = () => this.props
return {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cde631054f..fa8bc288c2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -677,6 +677,79 @@ importers:
specifier: 5.4.5
version: 5.4.5
+ examples/angular/row-selection-signal:
+ dependencies:
+ '@angular/animations':
+ specifier: ^17.3.9
+ version: 17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5))
+ '@angular/common':
+ specifier: ^17.3.9
+ version: 17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5))(rxjs@7.8.1)
+ '@angular/compiler':
+ specifier: ^17.3.9
+ version: 17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5))
+ '@angular/core':
+ specifier: ^17.3.9
+ version: 17.3.9(rxjs@7.8.1)(zone.js@0.14.5)
+ '@angular/forms':
+ specifier: ^17.3.9
+ version: 17.3.9(@angular/common@17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5))(rxjs@7.8.1))(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5))(@angular/platform-browser@17.3.9(@angular/animations@17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5)))(@angular/common@17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5))(rxjs@7.8.1))(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5)))(rxjs@7.8.1)
+ '@angular/platform-browser':
+ specifier: ^17.3.9
+ version: 17.3.9(@angular/animations@17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5)))(@angular/common@17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5))(rxjs@7.8.1))(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5))
+ '@angular/platform-browser-dynamic':
+ specifier: ^17.3.9
+ version: 17.3.9(@angular/common@17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5))(rxjs@7.8.1))(@angular/compiler@17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5)))(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5))(@angular/platform-browser@17.3.9(@angular/animations@17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5)))(@angular/common@17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5))(rxjs@7.8.1))(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5)))
+ '@faker-js/faker':
+ specifier: ^8.4.1
+ version: 8.4.1
+ '@tanstack/angular-table':
+ specifier: ^8.18.1
+ version: link:../../../packages/angular-table
+ rxjs:
+ specifier: ~7.8.1
+ version: 7.8.1
+ tslib:
+ specifier: ^2.6.2
+ version: 2.6.2
+ zone.js:
+ specifier: ~0.14.4
+ version: 0.14.5
+ devDependencies:
+ '@angular-devkit/build-angular':
+ specifier: ^17.3.8
+ version: 17.3.8(@angular/compiler-cli@17.3.9(@angular/compiler@17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5)))(typescript@5.4.5))(@types/express@4.17.21)(@types/node@20.12.7)(chokidar@3.6.0)(karma@6.4.3)(ng-packagr@17.3.0(@angular/compiler-cli@17.3.9(@angular/compiler@17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5)))(typescript@5.4.5))(tslib@2.6.2)(typescript@5.4.5))(typescript@5.4.5)
+ '@angular/cli':
+ specifier: ^17.3.8
+ version: 17.3.8(chokidar@3.6.0)
+ '@angular/compiler-cli':
+ specifier: ^17.3.9
+ version: 17.3.9(@angular/compiler@17.3.9(@angular/core@17.3.9(rxjs@7.8.1)(zone.js@0.14.5)))(typescript@5.4.5)
+ '@types/jasmine':
+ specifier: ~5.1.4
+ version: 5.1.4
+ jasmine-core:
+ specifier: ~5.1.2
+ version: 5.1.2
+ karma:
+ specifier: ~6.4.3
+ version: 6.4.3
+ karma-chrome-launcher:
+ specifier: ~3.2.0
+ version: 3.2.0
+ karma-coverage:
+ specifier: ~2.2.1
+ version: 2.2.1
+ karma-jasmine:
+ specifier: ~5.1.0
+ version: 5.1.0(karma@6.4.3)
+ karma-jasmine-html-reporter:
+ specifier: ~2.1.0
+ version: 2.1.0(jasmine-core@5.1.2)(karma-jasmine@5.1.0(karma@6.4.3))(karma@6.4.3)
+ typescript:
+ specifier: 5.4.5
+ version: 5.4.5
+
examples/angular/signal-input:
dependencies:
'@angular/animations':