diff --git a/examples/react/row-selection/package.json b/examples/react/row-selection/package.json index f54912b0da..15e58105cb 100644 --- a/examples/react/row-selection/package.json +++ b/examples/react/row-selection/package.json @@ -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", diff --git a/packages/angular-table/src/flex-render/renderer.ts b/packages/angular-table/src/flex-render/renderer.ts index 982bda8361..20064719ff 100644 --- a/packages/angular-table/src/flex-render/renderer.ts +++ b/packages/angular-table/src/flex-render/renderer.ts @@ -5,26 +5,24 @@ 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, @@ -32,6 +30,12 @@ import type { RowData, TableFeatures, } from '@tanstack/table-core' +import type { + EffectRef, + TemplateRef, + Type, + ViewContainerRef, +} from '@angular/core' /** * Content supported by the `flexRender` directive when declaring @@ -106,7 +110,10 @@ export class FlexViewRenderer< | HeaderContext, > { #renderFlags = FlexRenderFlags.ViewFirstRender - #renderView: FlexRenderView | null = null + #renderView: FlexRenderView< + FlexRenderViewAllowedType, + FlexRenderTypedContent + > | null = null #currentRenderEffectRef: EffectRef | null = null #content: () => FlexRenderInputContent #props: () => TProps @@ -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 | null { + ): FlexRenderView | null { if (content.kind === 'primitive') { return this.#renderStringContent(content) } else if (content.kind === 'templateRef') { diff --git a/packages/angular-table/src/flex-render/view.ts b/packages/angular-table/src/flex-render/view.ts index f327e20454..e72f3377cf 100644 --- a/packages/angular-table/src/flex-render/view.ts +++ b/packages/angular-table/src/flex-render/view.ts @@ -35,8 +35,14 @@ export function mapToFlexRenderTypedContent( } } +export type FlexRenderViewAllowedType = + | FlexRenderComponentRef + | EmbeddedViewRef + | null + export abstract class FlexRenderView< - TView extends FlexRenderComponentRef | EmbeddedViewRef | null, + TView extends FlexRenderViewAllowedType, + TContent extends FlexRenderTypedContent, > { readonly view: TView #previousContent: FlexRenderTypedContent | undefined @@ -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 + EmbeddedViewRef, + Extract > { constructor( initialContent: Extract< @@ -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 + FlexRenderComponentRef, + Extract > { constructor( initialContent: Extract< @@ -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) + ) + } } diff --git a/packages/angular-table/tests/flex-render/flex-render-table.test.ts b/packages/angular-table/tests/flex-render/flex-render-table.test.ts index a2982d189f..8a9a71cd12 100644 --- a/packages/angular-table/tests/flex-render/flex-render-table.test.ts +++ b/packages/angular-table/tests/flex-render/flex-render-table.test.ts @@ -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>('Initial status') @@ -546,3 +582,23 @@ class TestBadgeComponent { readonly status = input.required() } + +@Component({ + selector: 'app-test-a-badge', + template: `A {{ status() }}`, + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class TestABadgeComponent { + readonly status = input.required() +} + +@Component({ + selector: 'app-test-b-badge', + template: `B {{ status() }}`, + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class TestBBadgeComponent { + readonly status = input.required() +} diff --git a/packages/angular-table/tests/flex-render/flex-render.unit.test.ts b/packages/angular-table/tests/flex-render/flex-render.unit.test.ts index 96ce5241aa..d4193c8524 100644 --- a/packages/angular-table/tests/flex-render/flex-render.unit.test.ts +++ b/packages/angular-table/tests/flex-render/flex-render.unit.test.ts @@ -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 @@ -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 () => { diff --git a/packages/solid-table-devtools/src/production/plugin.tsx b/packages/solid-table-devtools/src/production/plugin.tsx index f8094f9571..6a878807c0 100644 --- a/packages/solid-table-devtools/src/production/plugin.tsx +++ b/packages/solid-table-devtools/src/production/plugin.tsx @@ -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[0] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a2a2c70a9..43a00ff7d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2228,7 +2228,7 @@ importers: specifier: ^6.0.3 version: 6.0.3(rollup@4.53.3) '@tanstack/react-devtools': - specifier: 0.10.1 + specifier: ^0.10.1 version: 0.10.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(solid-js@1.9.12) '@tanstack/react-table-devtools': specifier: 9.0.0-alpha.11