Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/react/row-selection/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
},
"devDependencies": {
"@rollup/plugin-replace": "^6.0.3",
"@tanstack/react-devtools": "0.10.1",
"@tanstack/react-devtools": "^0.10.1",
"@tanstack/react-table-devtools": "9.0.0-alpha.11",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
Expand Down
40 changes: 25 additions & 15 deletions packages/angular-table/src/flex-render/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,37 @@ import {
runInInjectionContext,
untracked,
} from '@angular/core'
import { TanStackTableToken } from '../helpers/table'
import { TanStackTableCellToken } from '../helpers/cell'
import { TanStackTableHeaderToken } from '../helpers/header'
import { TanStackTableToken } from '../helpers/table'
import { FlexRenderComponentProps } from './context'
import { FlexRenderFlags } from './flags'
import { flexRenderComponent } from './flexRenderComponent'
import { FlexRenderComponentFactory } from './flexRenderComponentFactory'
import {
FlexRenderComponentView,
FlexRenderTemplateView,
mapToFlexRenderTypedContent,
} from './view'
import { flexRenderComponent } from './flexRenderComponent'
import { FlexRenderComponentFactory } from './flexRenderComponentFactory'
import type { FlexRenderComponent } from './flexRenderComponent'
import type {
EffectRef,
TemplateRef,
Type,
ViewContainerRef,
} from '@angular/core'
import type { FlexRenderTypedContent, FlexRenderView } from './view'
FlexRenderTypedContent,
FlexRenderView,
FlexRenderViewAllowedType,
} from './view'
import type { FlexRenderComponent } from './flexRenderComponent'
import type {
CellContext,
CellData,
HeaderContext,
RowData,
TableFeatures,
} from '@tanstack/table-core'
import type {
EffectRef,
TemplateRef,
Type,
ViewContainerRef,
} from '@angular/core'

/**
* Content supported by the `flexRender` directive when declaring
Expand Down Expand Up @@ -106,7 +110,10 @@ export class FlexViewRenderer<
| HeaderContext<TFeatures, TRowData, TValue>,
> {
#renderFlags = FlexRenderFlags.ViewFirstRender
#renderView: FlexRenderView<any> | null = null
#renderView: FlexRenderView<
FlexRenderViewAllowedType,
FlexRenderTypedContent
> | null = null
#currentRenderEffectRef: EffectRef | null = null
#content: () => FlexRenderInputContent<TProps>
#props: () => TProps
Expand Down Expand Up @@ -257,18 +264,21 @@ export class FlexViewRenderer<
if (latestContent.kind === 'null' || !this.#renderView) {
this.#renderFlags |= FlexRenderFlags.ContentChanged
} else {
this.#renderView.content = latestContent
const { kind: previousKind } = this.#renderView.previousContent
if (latestContent.kind !== previousKind) {
const { kind: currentKind } = this.#renderView.content
if (
latestContent.kind !== currentKind ||
!this.#renderView.eq(latestContent)
) {
this.#renderFlags |= FlexRenderFlags.ContentChanged
}
this.#renderView.content = latestContent
}
this.#update()
}

#renderViewByContent(
content: FlexRenderTypedContent,
): FlexRenderView<any> | null {
): FlexRenderView<FlexRenderViewAllowedType, FlexRenderTypedContent> | null {
if (content.kind === 'primitive') {
return this.#renderStringContent(content)
} else if (content.kind === 'templateRef') {
Expand Down
48 changes: 45 additions & 3 deletions packages/angular-table/src/flex-render/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,14 @@ export function mapToFlexRenderTypedContent(
}
}

export type FlexRenderViewAllowedType =
| FlexRenderComponentRef<any>
| EmbeddedViewRef<unknown>
| null

export abstract class FlexRenderView<
TView extends FlexRenderComponentRef<any> | EmbeddedViewRef<unknown> | null,
TView extends FlexRenderViewAllowedType,
TContent extends FlexRenderTypedContent,
> {
readonly view: TView
#previousContent: FlexRenderTypedContent | undefined
Expand Down Expand Up @@ -69,11 +75,14 @@ export abstract class FlexRenderView<

abstract onDestroy(callback: Function): void

abstract eq(view: TContent): boolean

abstract unmount(): void
}

export class FlexRenderTemplateView extends FlexRenderView<
EmbeddedViewRef<unknown>
EmbeddedViewRef<unknown>,
Extract<FlexRenderTypedContent, { kind: 'primitive' | 'templateRef' }>
> {
constructor(
initialContent: Extract<
Expand Down Expand Up @@ -105,10 +114,27 @@ export class FlexRenderTemplateView extends FlexRenderView<
override onDestroy(callback: Function) {
this.view.onDestroy(callback)
}

override eq(
compare: Extract<
FlexRenderTypedContent,
{ kind: 'primitive' | 'templateRef' }
>,
): boolean {
return (
(this.content.kind === 'primitive' &&
compare.kind === 'primitive' &&
this.content.content === compare.content) ||
(this.content.kind === 'templateRef' &&
compare.kind === 'templateRef' &&
this.content.content === compare.content)
)
}
}

export class FlexRenderComponentView extends FlexRenderView<
FlexRenderComponentRef<unknown>
FlexRenderComponentRef<unknown>,
Extract<FlexRenderTypedContent, { kind: 'component' | 'flexRenderComponent' }>
> {
constructor(
initialContent: Extract<
Expand Down Expand Up @@ -162,4 +188,20 @@ export class FlexRenderComponentView extends FlexRenderView<
override onDestroy(callback: Function) {
this.view.componentRef.onDestroy(callback)
}

override eq(
compare: Extract<
FlexRenderTypedContent,
{ kind: 'component' | 'flexRenderComponent' }
>,
): boolean {
return (
(this.content.kind === 'component' &&
compare.kind === 'component' &&
this.content.content === compare.content) ||
(this.content.kind === 'flexRenderComponent' &&
compare.kind === 'flexRenderComponent' &&
this.content.content.component === compare.content.component)
)
}
}
56 changes: 56 additions & 0 deletions packages/angular-table/tests/flex-render/flex-render-table.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,42 @@ describe('FlexRenderDirective', () => {
expect(firstCell!.textContent).toEqual('Updated status')
})

test('Render content reactively when flexRenderComponent class changes', async () => {
const showBadgeA = signal(true)

const { dom, fixture } = createTestTable(defaultData, [
{
id: 'first_cell',
header: 'Status',
cell: () => {
return showBadgeA()
? flexRenderComponent(TestABadgeComponent, {
inputs: { status: 'From A' },
})
: flexRenderComponent(TestBBadgeComponent, {
inputs: { status: 'From B' },
})
},
},
])

const row = dom.getBodyRow(0)!
const firstCell = row.querySelector('td')!

let firstElement = firstCell.firstElementChild as HTMLElement | null
expect(firstElement).not.toBeNull()
expect(firstElement!.tagName).toEqual('APP-TEST-A-BADGE')
expect(firstCell.textContent).toContain('From A')

showBadgeA.set(false)
fixture.detectChanges()

firstElement = firstCell.firstElementChild as HTMLElement | null
expect(firstElement).not.toBeNull()
expect(firstElement!.tagName).toEqual('APP-TEST-B-BADGE')
expect(firstCell.textContent).toContain('From B')
})

test('Render content reactively based on signal value', async () => {
const statusComponent = signal<FlexRenderContent<any>>('Initial status')

Expand Down Expand Up @@ -546,3 +582,23 @@ class TestBadgeComponent {

readonly status = input.required<string>()
}

@Component({
selector: 'app-test-a-badge',
template: `<span>A {{ status() }}</span>`,
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
})
class TestABadgeComponent {
readonly status = input.required<string>()
}

@Component({
selector: 'app-test-b-badge',
template: `<span>B {{ status() }}</span>`,
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
})
class TestBBadgeComponent {
readonly status = input.required<string>()
}
52 changes: 47 additions & 5 deletions packages/angular-table/tests/flex-render/flex-render.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { Component, ViewChild, input } from '@angular/core'
import { TestBed } from '@angular/core/testing'
import {
Component,
input,
signal,
ViewChild,
type TemplateRef,
} from '@angular/core'
import { TestBed, type ComponentFixture } from '@angular/core/testing'
import { createColumnHelper } from '@tanstack/table-core'
import { describe, expect, test } from 'vitest'
import {
FlexRender,
FlexRenderDirective,
flexRenderComponent,
FlexRenderDirective,
injectFlexRenderContext,
} from '../../src'
import { setFixtureSignalInput, setFixtureSignalInputs } from '../test-utils'
import type { TemplateRef } from '@angular/core'
import type { ComponentFixture } from '@angular/core/testing'

interface Data {
id: string
Expand Down Expand Up @@ -129,6 +133,44 @@ describe('FlexRenderDirective', () => {
expect(fixture.nativeElement.textContent).toEqual('Updated value')
})

test('should rerender when content has conditional return with different component types', () => {
@Component({
selector: 'app-fake-a',
template: `A component`,
standalone: true,
})
class FakeComponentA {
context = injectFlexRenderContext<{ property: string }>()
}

@Component({
selector: 'app-fake-b',
template: `B component`,
standalone: true,
})
class FakeComponentB {}

const fixture = TestBed.createComponent(TestRenderComponent)
const showB = signal(false)

setFixtureSignalInputs(fixture, {
content: () => {
return showB()
? flexRenderComponent(FakeComponentB)
: flexRenderComponent(FakeComponentA)
},
context: {},
})

expect(fixture.nativeElement.textContent).toEqual('A component')

showB.set(true)

fixture.detectChanges()

expect(fixture.nativeElement.textContent).toEqual('B component')
})

// Skip for now, test framework (using ComponentRef.setInput) cannot recognize signal inputs
// as component inputs
test('should render custom components', async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/solid-table-devtools/src/production/plugin.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createSolidPlugin } from '@tanstack/devtools-utils/solid'
import { TableDevtoolsPanel } from './TableDevtools'
import type { TanStackDevtoolsPlugin } from '@tanstack/devtools'

type SolidTableDevtoolsPlugin = ReturnType<
ReturnType<typeof createSolidPlugin>[0]
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading