From 2a7ff01e88a85ef67b2990e4b1f65ee39918dfc5 Mon Sep 17 00:00:00 2001 From: Fahri Gedik <53567152+fahrigedik@users.noreply.github.com> Date: Mon, 12 Jan 2026 22:46:04 +0300 Subject: [PATCH] Add extensible table row detail feature Introduces the ExtensibleTableRowDetailComponent for expandable row details in extensible tables. Updates the extensible table to support row detail templates via both direct input and content child component, adds toggle logic and emits rowDetailToggle events. Documentation and exports are updated accordingly. --- .../ui/angular/extensible-table-row-detail.md | 189 ++++++++++++++++++ .../extensible-table-row-detail.component.ts | 10 + .../extensible-table-row-detail/index.ts | 1 + .../extensible-table.component.html | 169 +++++++--------- .../extensible-table.component.ts | 36 +++- .../extensible/src/lib/components/index.ts | 1 + .../components/extensible/src/public-api.ts | 1 + 7 files changed, 311 insertions(+), 96 deletions(-) create mode 100644 docs/en/framework/ui/angular/extensible-table-row-detail.md create mode 100644 npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table-row-detail/extensible-table-row-detail.component.ts create mode 100644 npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table-row-detail/index.ts diff --git a/docs/en/framework/ui/angular/extensible-table-row-detail.md b/docs/en/framework/ui/angular/extensible-table-row-detail.md new file mode 100644 index 0000000000..24ae0f3b09 --- /dev/null +++ b/docs/en/framework/ui/angular/extensible-table-row-detail.md @@ -0,0 +1,189 @@ +```json +//[doc-seo] +{ + "Description": "Learn how to add expandable row details to data tables using the Extensible Table Row Detail component in ABP Framework Angular UI." +} +``` + +# Extensible Table Row Detail for Angular UI + +## Introduction + +The `` component allows you to add expandable row details to any ``. When users click the expand icon, additional content is revealed below the row. + +Extensible Table Row Detail Example + +## Quick Start + +### Step 1. Import the Component + +Import `ExtensibleTableRowDetailComponent` in your component: + +```typescript +import { + ExtensibleTableComponent, + ExtensibleTableRowDetailComponent +} from '@abp/ng.components/extensible'; + +@Component({ + // ... + imports: [ + ExtensibleTableComponent, + ExtensibleTableRowDetailComponent, + ], +}) +export class MyComponent { } +``` + +### Step 2. Add Row Detail Template + +Place `` inside `` with an `ng-template`: + +```html + + + +
+
{{ row.name }}
+

ID: {{ row.id }}

+

Status: {{ row.isActive ? 'Active' : 'Inactive' }}

+
+
+
+
+``` + +An expand/collapse chevron icon will automatically appear in the first column of each row. + +## API + +### ExtensibleTableRowDetailComponent + +| Input | Type | Default | Description | +|-------|------|---------|-------------| +| `rowHeight` | `string \| number` | `'100%'` | Height of the expanded row detail area | + +### Template Context Variables + +| Variable | Type | Description | +|----------|------|-------------| +| `row` | `R` | The current row data object | +| `expanded` | `boolean` | Whether the row is currently expanded | + +## Usage Examples + +### Basic Example + +Display additional information when a row is expanded: + +```html + + + +
+ Details for: {{ row.name }} +
{{ row | json }}
+
+
+
+
+``` + +### With Custom Row Height + +Specify a fixed height for the detail area: + +```html + + +
Fixed 200px height content
+
+
+``` + +### Using Expanded State + +Apply conditional styling based on expansion state: + +```html + + +
+

This row is {{ expanded ? 'expanded' : 'collapsed' }}

+
+
+
+``` + +### With Badges and Localization + +```html + + +
+
+
+

{{ 'MyModule::Name' | abpLocalization }}:

+

{{ row.name }}

+
+
+

{{ 'MyModule::Status' | abpLocalization }}:

+

+ @if (row.isActive) { + {{ 'AbpUi::Yes' | abpLocalization }} + } @else { + {{ 'AbpUi::No' | abpLocalization }} + } +

+
+
+
+
+
+``` + +## Alternative: Direct Template Input + +For simpler use cases, you can use the `rowDetailTemplate` input on `` directly: + +```html + + + +
{{ row.name }}
+
+``` + +## Events + +### rowDetailToggle + +The `rowDetailToggle` output emits when a row is expanded or collapsed: + +```html + + + ... + + +``` + +```typescript +onRowToggle(row: MyDto) { + console.log('Row toggled:', row); +} +``` + +## See Also + +- [Data Table Column Extensions](data-table-column-extensions.md) +- [Entity Action Extensions](entity-action-extensions.md) +- [Extensions Overview](extensions-overall.md) diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table-row-detail/extensible-table-row-detail.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table-row-detail/extensible-table-row-detail.component.ts new file mode 100644 index 0000000000..aabbd2a255 --- /dev/null +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table-row-detail/extensible-table-row-detail.component.ts @@ -0,0 +1,10 @@ +import { Component, contentChild, input, TemplateRef } from '@angular/core'; + +@Component({ + selector: 'abp-extensible-table-row-detail', + template: '', +}) +export class ExtensibleTableRowDetailComponent { + readonly rowHeight = input('100%'); + readonly template = contentChild(TemplateRef<{ row: R; expanded: boolean }>); +} diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table-row-detail/index.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table-row-detail/index.ts new file mode 100644 index 0000000000..e0666787b4 --- /dev/null +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table-row-detail/index.ts @@ -0,0 +1 @@ +export * from './extensible-table-row-detail.component'; diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html index 8581f6c4d4..a42d5d32de 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html @@ -1,21 +1,30 @@ @if (isBrowser) { - -@if(selectable) { - + + @if (effectiveRowDetailTemplate) { + + + + + + + + + + + + + + } + @if(selectable) { + @@ -44,97 +53,67 @@ } @if (actionsTemplate || (actionList.length && hasAtLeastOnePermittedAction)) { - - - - - @if (isVisibleActions(row)) { - - } - + + + + + @if (isVisibleActions(row)) { + + } - + + } @for (prop of propList; track prop.name; let i = $index) { - - - @if (prop.tooltip) { - - {{ column.name }} - - } @else { - - {{ column.name }} - - } - - - - - @if (!row['_' + prop.name].component) { - @if (prop.type === 'datetime' || prop.type === 'date' || prop.type === 'time') { -
+ + @if (prop.tooltip) { + + {{ column.name }} + + } @else { + + {{ column.name }} + + } + + + + + @if (!row['_' + prop.name].component) { + @if (prop.type === 'datetime' || prop.type === 'date' || prop.type === 'time') { +
- } @else { -
+ } @else { +
- } - } @else { -
+ } + } @else { + - } -
+ ">
+ } -
-
+ + +
}
-} +} \ No newline at end of file diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts index c70d2b39e7..bba805b684 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts @@ -4,6 +4,7 @@ import { ChangeDetectorRef, Component, computed, + ContentChild, EventEmitter, inject, Injector, @@ -17,13 +18,14 @@ import { SimpleChanges, TemplateRef, TrackByFunction, + ViewChild, } from '@angular/core'; import { AsyncPipe, isPlatformBrowser, NgComponentOutlet, NgTemplateOutlet } from '@angular/common'; import { Observable, filter, map, Subject, debounceTime, distinctUntilChanged } from 'rxjs'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; -import { NgxDatatableModule, SelectionType } from '@swimlane/ngx-datatable'; +import { NgxDatatableModule, SelectionType, DatatableComponent } from '@swimlane/ngx-datatable'; import { ABP, @@ -53,6 +55,7 @@ import { ROW_RECORD, } from '../../tokens/extensions.token'; import { GridActionsComponent } from '../grid-actions/grid-actions.component'; +import { ExtensibleTableRowDetailComponent } from './extensible-table-row-detail'; const DEFAULT_ACTIONS_COLUMN_WIDTH = 150; @@ -75,6 +78,12 @@ const DEFAULT_ACTIONS_COLUMN_WIDTH = 150; ], templateUrl: './extensible-table.component.html', changeDetection: ChangeDetectionStrategy.OnPush, + styles: [` + :host ::ng-deep .ngx-datatable.material .datatable-body .datatable-row-detail { + background: none; + padding: 0; + } + `], }) export class ExtensibleTableComponent implements OnChanges, AfterViewInit, OnDestroy { readonly #injector = inject(Injector); @@ -127,6 +136,26 @@ export class ExtensibleTableComponent implements OnChanges, AfterViewIn @Output() loadMore = new EventEmitter(); @Input() tableHeight: number; + // Row detail configuration (supports both direct template and child component) + @Input() rowDetailTemplate?: TemplateRef<{ row: R; expanded: boolean }>; + @Input() rowDetailHeight: string | number = '100%'; + @Output() rowDetailToggle = new EventEmitter(); + + @ContentChild(ExtensibleTableRowDetailComponent) + rowDetailComponent?: ExtensibleTableRowDetailComponent; + + @ViewChild('table') table!: DatatableComponent; + + /** Gets the effective row detail template (from component or direct input) */ + get effectiveRowDetailTemplate(): TemplateRef | undefined { + return this.rowDetailComponent?.template() ?? this.rowDetailTemplate; + } + + /** Gets the effective row detail height */ + get effectiveRowDetailHeight(): string | number { + return this.rowDetailComponent?.rowHeight() ?? this.rowDetailHeight; + } + hasAtLeastOnePermittedAction: boolean; readonly propList: EntityPropList; @@ -290,6 +319,11 @@ export class ExtensibleTableComponent implements OnChanges, AfterViewIn return this.tableHeight ? `${this.tableHeight}px` : 'auto'; } + toggleExpandRow(row: R): void { + this.table?.rowDetail?.toggleExpandRow(row); + this.rowDetailToggle.emit(row); + } + ngAfterViewInit(): void { if (!this.infiniteScroll) { this.list?.requestStatus$?.pipe(filter(status => status === 'loading')).subscribe(() => { diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/index.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/index.ts index 149043089b..ee501c875d 100644 --- a/npm/ng-packs/packages/components/extensible/src/lib/components/index.ts +++ b/npm/ng-packs/packages/components/extensible/src/lib/components/index.ts @@ -2,6 +2,7 @@ export * from './abstract-actions/abstract-actions.component'; export * from './extensible-form/extensible-form.component'; export * from './extensible-form/extensible-form-prop.component'; export * from './extensible-table/extensible-table.component'; +export * from './extensible-table/extensible-table-row-detail'; export * from './date-time-picker/extensible-date-time-picker.component'; export * from './grid-actions/grid-actions.component'; export * from './page-toolbar/page-toolbar.component'; diff --git a/npm/ng-packs/packages/components/extensible/src/public-api.ts b/npm/ng-packs/packages/components/extensible/src/public-api.ts index e5c7883738..5d06088a02 100644 --- a/npm/ng-packs/packages/components/extensible/src/public-api.ts +++ b/npm/ng-packs/packages/components/extensible/src/public-api.ts @@ -2,6 +2,7 @@ export * from './lib/components/date-time-picker/extensible-date-time-picker.com export * from './lib/components/extensible-form/extensible-form-prop.component'; export * from './lib/components/extensible-form/extensible-form.component'; export * from './lib/components/extensible-table/extensible-table.component'; +export * from './lib/components/extensible-table/extensible-table-row-detail'; export * from './lib/components/grid-actions/grid-actions.component'; export * from './lib/components/page-toolbar/page-toolbar.component'; export * from './lib/components/multi-select';