From 3a76a03d97a934907a33585c11382dc9b3fe5113 Mon Sep 17 00:00:00 2001 From: Mamerto Giango Date: Sat, 18 May 2024 17:45:43 +0800 Subject: [PATCH 1/3] feat(angular-table) refactor flex-renderer to use signal - convert all inputs to signal - re-render when content is updated (is this expected?) - make rendering more flexible by handling function based content first --- packages/angular-table/src/flex-render.ts | 79 ++++++++--------------- 1 file changed, 26 insertions(+), 53 deletions(-) diff --git a/packages/angular-table/src/flex-render.ts b/packages/angular-table/src/flex-render.ts index 46242761d9..5081eb899b 100644 --- a/packages/angular-table/src/flex-render.ts +++ b/packages/angular-table/src/flex-render.ts @@ -1,17 +1,15 @@ import { - ChangeDetectorRef, ComponentRef, Directive, - type DoCheck, EmbeddedViewRef, - inject, InjectionToken, Injector, - Input, - type OnInit, TemplateRef, - type Type, ViewContainerRef, + effect, + inject, + input, + type Type, } from '@angular/core' type FlexRenderContent> = @@ -25,61 +23,36 @@ type FlexRenderContent> = selector: '[flexRender]', standalone: true, }) -export class FlexRenderDirective> - implements OnInit, DoCheck -{ - @Input({ required: true, alias: 'flexRender' }) - content: - | number - | string - | ((props: TProps) => FlexRenderContent) - | undefined = undefined - - @Input({ required: true, alias: 'flexRenderProps' }) - props: TProps = {} as TProps - - @Input({ required: false, alias: 'flexRenderInjector' }) - injector: Injector = inject(Injector) +export class FlexRenderDirective> { + // set the type to unknown as input signal is not able to recognize the types correctly + // which causes build error + content = input.required({ alias: 'flexRender' }) - constructor( - private viewContainerRef: ViewContainerRef, - private templateRef: TemplateRef - ) {} + props = input({} as TProps, { alias: 'flexRenderProps' }) - ref?: ComponentRef | EmbeddedViewRef | null = null + injector = input(inject(Injector), { alias: 'flexRenderInjector' }) - ngOnInit(): void { - this.ref = this.render() - } + viewContainerRef = inject(ViewContainerRef) + templateRef = inject(TemplateRef) - ngDoCheck() { - if (this.ref instanceof ComponentRef) { - this.ref.injector.get(ChangeDetectorRef).markForCheck() - } else if (this.ref instanceof EmbeddedViewRef) { - this.ref.markForCheck() - } + constructor() { + effect(() => this.render()) } render() { this.viewContainerRef.clear() - const { content, props } = this - if (!this.content) { + const content = this.content() + if (!content) { return null } - if (typeof content === 'string' || typeof content === 'number') { - return this.renderStringContent() - } if (typeof content === 'function') { - return this.renderContent(content(props)) + return this.renderContent(content(this.props())) } - return null + return this.renderStringContent() } private renderContent(content: FlexRenderContent) { - if (typeof content === 'string' || typeof content === 'number') { - return this.renderStringContent() - } if (content instanceof TemplateRef) { return this.viewContainerRef.createEmbeddedView( content, @@ -87,6 +60,8 @@ export class FlexRenderDirective> ) } else if (content instanceof FlexRenderComponent) { return this.renderComponent(content) + } else if (content) { + return this.renderStringContent() } else { return null } @@ -94,10 +69,8 @@ export class FlexRenderDirective> private renderStringContent(): EmbeddedViewRef { const context = () => { - return typeof this.content === 'string' || - typeof this.content === 'number' - ? this.content - : this.content?.(this.props) + const content = this.content() + return typeof content === 'function' ? content?.(this.props()) : content } return this.viewContainerRef.createEmbeddedView(this.templateRef, { get $implicit() { @@ -111,14 +84,14 @@ export class FlexRenderDirective> ): ComponentRef { const { component, inputs, injector } = flexRenderComponent - const getContext = () => this.props + const getContext = () => this.props() - const proxy = new Proxy(this.props, { + const proxy = new Proxy(this.props(), { get: (_, key) => getContext()?.[key as keyof typeof _], }) const componentInjector = Injector.create({ - parent: injector ?? this.injector, + parent: injector ?? this.injector(), providers: [{ provide: FlexRenderComponentProps, useValue: proxy }], }) @@ -134,7 +107,7 @@ export class FlexRenderDirective> } private getTemplateRefContext() { - const getContext = () => this.props + const getContext = () => this.props() return { get $implicit() { return getContext() From a56c687d5c299f0a81b0b57fdd086c88011094a3 Mon Sep 17 00:00:00 2001 From: Mamerto Giango Date: Tue, 21 May 2024 20:30:31 +0800 Subject: [PATCH 2/3] feat(angular-table): Enable the use of signal based input to Renderable component - fix the broken implementation by allowing the consumer to provide the Component Type to be rendered - allowing the consumer to define the input as signal inside the Component to be rendered --- .../row-selection/src/app/app.component.ts | 12 +-- .../src/app/selection-column.component.ts | 31 +++++--- packages/angular-table/src/flex-render.ts | 75 ++++++------------- packages/angular-table/src/index.ts | 4 +- 4 files changed, 44 insertions(+), 78 deletions(-) diff --git a/examples/angular/row-selection/src/app/app.component.ts b/examples/angular/row-selection/src/app/app.component.ts index 8711fd3959..5e2e103b4a 100644 --- a/examples/angular/row-selection/src/app/app.component.ts +++ b/examples/angular/row-selection/src/app/app.component.ts @@ -9,7 +9,6 @@ import { import { ColumnDef, createAngularTable, - FlexRenderComponent, FlexRenderDirective, getCoreRowModel, getFilteredRowModel, @@ -18,7 +17,6 @@ import { } from '@tanstack/angular-table' import { FilterComponent } from './filter' import { makeData, type Person } from './makeData' -import { FormsModule } from '@angular/forms' import { TableHeadSelectionComponent, TableRowSelectionComponent, @@ -27,7 +25,7 @@ import { @Component({ selector: 'app-root', standalone: true, - imports: [FilterComponent, FlexRenderDirective, FormsModule], + imports: [FilterComponent, FlexRenderDirective], templateUrl: './app.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) @@ -42,12 +40,8 @@ export class AppComponent { readonly columns: ColumnDef[] = [ { id: 'select', - header: () => { - return new FlexRenderComponent(TableHeadSelectionComponent) - }, - cell: () => { - return new FlexRenderComponent(TableRowSelectionComponent) - }, + header: () => TableHeadSelectionComponent, + cell: () => TableRowSelectionComponent, }, { header: 'Name', diff --git a/examples/angular/row-selection/src/app/selection-column.component.ts b/examples/angular/row-selection/src/app/selection-column.component.ts index b4f3e1c008..47dc318d73 100644 --- a/examples/angular/row-selection/src/app/selection-column.component.ts +++ b/examples/angular/row-selection/src/app/selection-column.component.ts @@ -1,17 +1,16 @@ +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { - type CellContext, - type HeaderContext, - injectFlexRenderContext, -} from '@tanstack/angular-table' -import { ChangeDetectionStrategy, Component } from '@angular/core' + Row, + Table +} from '@tanstack/angular-table'; @Component({ template: ` `, host: { @@ -21,15 +20,23 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' changeDetection: ChangeDetectionStrategy.OnPush, }) export class TableHeadSelectionComponent { - context = injectFlexRenderContext>() + // 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: { @@ -39,5 +46,5 @@ export class TableHeadSelectionComponent { changeDetection: ChangeDetectionStrategy.OnPush, }) export class TableRowSelectionComponent { - context = injectFlexRenderContext>() + row = input.required>(); } diff --git a/packages/angular-table/src/flex-render.ts b/packages/angular-table/src/flex-render.ts index 5081eb899b..0e175894b2 100644 --- a/packages/angular-table/src/flex-render.ts +++ b/packages/angular-table/src/flex-render.ts @@ -2,20 +2,18 @@ import { ComponentRef, Directive, EmbeddedViewRef, - InjectionToken, Injector, TemplateRef, + Type, ViewContainerRef, - effect, inject, - input, - type Type, + input } from '@angular/core' type FlexRenderContent> = | string | number - | FlexRenderComponent + | Type | TemplateRef<{ $implicit: TProps }> | null @@ -30,13 +28,12 @@ export class FlexRenderDirective> { props = input({} as TProps, { alias: 'flexRenderProps' }) - injector = input(inject(Injector), { alias: 'flexRenderInjector' }) + private readonly injector = inject(Injector) + private readonly viewContainerRef = inject(ViewContainerRef) + private readonly templateRef = inject(TemplateRef) - viewContainerRef = inject(ViewContainerRef) - templateRef = inject(TemplateRef) - - constructor() { - effect(() => this.render()) + ngOnInit(): void { + this.render() } render() { @@ -49,7 +46,7 @@ export class FlexRenderDirective> { if (typeof content === 'function') { return this.renderContent(content(this.props())) } - return this.renderStringContent() + return this.renderStringContent(content as FlexRenderContent) } private renderContent(content: FlexRenderContent) { @@ -58,20 +55,19 @@ export class FlexRenderDirective> { content, this.getTemplateRefContext() ) - } else if (content instanceof FlexRenderComponent) { + } else if (content instanceof Type) { return this.renderComponent(content) } else if (content) { - return this.renderStringContent() + return this.renderStringContent(content) } else { return null } } - private renderStringContent(): EmbeddedViewRef { - const context = () => { - const content = this.content() - return typeof content === 'function' ? content?.(this.props()) : content - } + private renderStringContent( + content: FlexRenderContent + ): EmbeddedViewRef { + const context = () => content return this.viewContainerRef.createEmbeddedView(this.templateRef, { get $implicit() { return context() @@ -79,28 +75,15 @@ export class FlexRenderDirective> { }) } - private renderComponent( - flexRenderComponent: FlexRenderComponent - ): ComponentRef { - const { component, inputs, injector } = flexRenderComponent - - const getContext = () => this.props() - - const proxy = new Proxy(this.props(), { - get: (_, key) => getContext()?.[key as keyof typeof _], - }) - - const componentInjector = Injector.create({ - parent: injector ?? this.injector(), - providers: [{ provide: FlexRenderComponentProps, useValue: proxy }], - }) - + private renderComponent(component: Type): ComponentRef { const componentRef = this.viewContainerRef.createComponent(component, { - injector: componentInjector, + injector: this.injector, }) - for (const prop in inputs) { + const props = this.props() + for (const prop in props) { + // Only signal based input can be added here if (componentRef.instance?.hasOwnProperty(prop)) { - componentRef.setInput(prop, inputs[prop]) + componentRef.setInput(prop, props[prop]) } } return componentRef @@ -115,19 +98,3 @@ export class FlexRenderDirective> { } } } - -export class FlexRenderComponent> { - constructor( - readonly component: Type, - readonly inputs: T = {} as T, - readonly injector?: Injector - ) {} -} - -const FlexRenderComponentProps = new InjectionToken>( - '[@tanstack/angular-table] Flex render component context props' -) - -export function injectFlexRenderContext>(): T { - return inject(FlexRenderComponentProps) -} diff --git a/packages/angular-table/src/index.ts b/packages/angular-table/src/index.ts index b702caeb74..976d88df0a 100644 --- a/packages/angular-table/src/index.ts +++ b/packages/angular-table/src/index.ts @@ -13,9 +13,7 @@ import { proxifyTable } from './proxy' export * from '@tanstack/table-core' export { - FlexRenderComponent, - FlexRenderDirective, - injectFlexRenderContext, + FlexRenderDirective } from './flex-render' export function createAngularTable( From 00d2fd90e7ba1458eaefc553dd25592ccbd22867 Mon Sep 17 00:00:00 2001 From: Mamerto Giango Date: Tue, 21 May 2024 21:53:49 +0800 Subject: [PATCH 3/3] feat(angular-table): fix PR check error --- .../src/app/selection-column.component.ts | 11 ++++------- packages/angular-table/src/flex-render.ts | 2 +- packages/angular-table/src/index.ts | 4 +--- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/examples/angular/row-selection/src/app/selection-column.component.ts b/examples/angular/row-selection/src/app/selection-column.component.ts index 47dc318d73..e10015a149 100644 --- a/examples/angular/row-selection/src/app/selection-column.component.ts +++ b/examples/angular/row-selection/src/app/selection-column.component.ts @@ -1,8 +1,5 @@ -import { ChangeDetectionStrategy, Component, input } from '@angular/core'; -import { - Row, - Table -} from '@tanstack/angular-table'; +import { ChangeDetectionStrategy, Component, input } from '@angular/core' +import { Row, Table } from '@tanstack/angular-table' @Component({ template: ` @@ -28,7 +25,7 @@ export class TableHeadSelectionComponent { //column = input.required>(); //header = input.required>(); - table = input.required>(); + table = input.required>() } @Component({ @@ -46,5 +43,5 @@ export class TableHeadSelectionComponent { changeDetection: ChangeDetectionStrategy.OnPush, }) export class TableRowSelectionComponent { - row = input.required>(); + row = input.required>() } diff --git a/packages/angular-table/src/flex-render.ts b/packages/angular-table/src/flex-render.ts index 0e175894b2..9f44547744 100644 --- a/packages/angular-table/src/flex-render.ts +++ b/packages/angular-table/src/flex-render.ts @@ -7,7 +7,7 @@ import { Type, ViewContainerRef, inject, - input + input, } from '@angular/core' type FlexRenderContent> = diff --git a/packages/angular-table/src/index.ts b/packages/angular-table/src/index.ts index 976d88df0a..3870cda464 100644 --- a/packages/angular-table/src/index.ts +++ b/packages/angular-table/src/index.ts @@ -12,9 +12,7 @@ import { proxifyTable } from './proxy' export * from '@tanstack/table-core' -export { - FlexRenderDirective -} from './flex-render' +export { FlexRenderDirective } from './flex-render' export function createAngularTable( options: () => TableOptions