From 81d9b6d74d014e5e1dcf4f34f305573666990e8e Mon Sep 17 00:00:00 2001 From: maksymkaD <52499153+maksymkaD@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:22:37 +0300 Subject: [PATCH] Implement ngx-fm-group tag --- .../angular/src/flexmonster.module.ts | 3 + .../angular/src/group.component.ts | 75 +++++++++++++++++++ .../flexmonster/angular/src/public-api.ts | 1 + .../angular/ssr/group.component.ts | 75 +++++++++++++++++++ .../flexmonster/angular/ssr/public-api.ts | 1 + projects/test-app/src/app/app.html | 6 ++ projects/test-app/src/app/app.ts | 48 +++++++++++- 7 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 projects/flexmonster/angular/src/group.component.ts create mode 100644 projects/flexmonster/angular/ssr/group.component.ts diff --git a/projects/flexmonster/angular/src/flexmonster.module.ts b/projects/flexmonster/angular/src/flexmonster.module.ts index aa56750..6a645bf 100644 --- a/projects/flexmonster/angular/src/flexmonster.module.ts +++ b/projects/flexmonster/angular/src/flexmonster.module.ts @@ -6,6 +6,7 @@ import { FMPivotTable } from './pivot-table.component'; import { FMPivotFieldList } from './pivot-field-list.component'; import { FMToolbar } from './toolbar.component'; import { FMFilter } from './filter.component'; +import { FMGroup } from './group.component'; @NgModule({ imports: [ @@ -16,6 +17,7 @@ import { FMFilter } from './filter.component'; FMPivotFieldList, FMToolbar, FMFilter, + FMGroup, ], exports: [ FMFlexmonster, @@ -25,6 +27,7 @@ import { FMFilter } from './filter.component'; FMPivotFieldList, FMToolbar, FMFilter, + FMGroup, ] }) export class FMModule{ } diff --git a/projects/flexmonster/angular/src/group.component.ts b/projects/flexmonster/angular/src/group.component.ts new file mode 100644 index 0000000..01681f3 --- /dev/null +++ b/projects/flexmonster/angular/src/group.component.ts @@ -0,0 +1,75 @@ +import { AfterContentInit, Component, ContentChildren, Input, QueryList } from '@angular/core'; +import type { StateInputParams } from '@flexmonster/js'; +import { FMFlexmonster } from './flexmonster.component'; +import { FMPivotTable } from './pivot-table.component'; +import { FMPivotFieldList } from './pivot-field-list.component'; +import { FMFlatTable } from './flat-table.component'; +import { FMFlatFieldList } from './flat-field-list.component'; +import { FMToolbar } from './toolbar.component'; +import { FMFilter } from './filter.component'; + +/** Every Flexmonster control wrapper exposes a `state` input. */ +type FMControl = { state: StateInputParams | undefined }; + +/** + * `FMGroup` links several Flexmonster controls to one shared state — the + * Angular equivalent of the React `FMGroup` wrapper. + * + * Place the controls you want to keep in sync as **direct children** of + * `` and give the group a `state`. The group passes that state + * into each control child that doesn't already have its own `state` (a + * child's own `state` always wins). Each child registers and reads the state + * through the same `@flexmonster/js` instance that creates its control, so + * there is no registry mismatch and the library's `` custom element + * is not needed. Controls that share the same `state.id` are linked by the + * library and stay in sync. + * + * React clones each child element to inject the `state` prop; Angular cannot + * clone projected content, so the group instead grabs its control children + * via `@ContentChildren` and sets their `state` input directly. The result is + * the same: a thin wrapper that propagates one state to its children, leaving + * the control components untouched. + * + * The group renders its children unchanged (just `` — no extra + * wrapper element). Only direct children are handled. + * + * @example + * + * + * + * + */ +@Component({ + selector: 'ngx-fm-group', + standalone: true, + template: '', +}) +export class FMGroup implements AfterContentInit { + @Input() state: StateInputParams | undefined; + + @ContentChildren(FMFlexmonster) private composites!: QueryList; + @ContentChildren(FMPivotTable) private pivotTables!: QueryList; + @ContentChildren(FMPivotFieldList) private pivotFieldLists!: QueryList; + @ContentChildren(FMFlatTable) private flatTables!: QueryList; + @ContentChildren(FMFlatFieldList) private flatFieldLists!: QueryList; + @ContentChildren(FMToolbar) private toolbars!: QueryList; + @ContentChildren(FMFilter) private filters!: QueryList; + + ngAfterContentInit(): void { + const controls: FMControl[] = [ + ...this.composites, + ...this.pivotTables, + ...this.pivotFieldLists, + ...this.flatTables, + ...this.flatFieldLists, + ...this.toolbars, + ...this.filters, + ]; + // A child's own state wins; otherwise inject the group's state. + for (const control of controls) { + if (control.state === undefined) { + control.state = this.state; + } + } + } +} diff --git a/projects/flexmonster/angular/src/public-api.ts b/projects/flexmonster/angular/src/public-api.ts index 941259d..79b491e 100644 --- a/projects/flexmonster/angular/src/public-api.ts +++ b/projects/flexmonster/angular/src/public-api.ts @@ -10,5 +10,6 @@ export * from './pivot-table.component'; export * from './pivot-field-list.component'; export * from './toolbar.component'; export * from './filter.component'; +export * from './group.component'; export * from './flexmonster.module'; \ No newline at end of file diff --git a/projects/flexmonster/angular/ssr/group.component.ts b/projects/flexmonster/angular/ssr/group.component.ts new file mode 100644 index 0000000..01681f3 --- /dev/null +++ b/projects/flexmonster/angular/ssr/group.component.ts @@ -0,0 +1,75 @@ +import { AfterContentInit, Component, ContentChildren, Input, QueryList } from '@angular/core'; +import type { StateInputParams } from '@flexmonster/js'; +import { FMFlexmonster } from './flexmonster.component'; +import { FMPivotTable } from './pivot-table.component'; +import { FMPivotFieldList } from './pivot-field-list.component'; +import { FMFlatTable } from './flat-table.component'; +import { FMFlatFieldList } from './flat-field-list.component'; +import { FMToolbar } from './toolbar.component'; +import { FMFilter } from './filter.component'; + +/** Every Flexmonster control wrapper exposes a `state` input. */ +type FMControl = { state: StateInputParams | undefined }; + +/** + * `FMGroup` links several Flexmonster controls to one shared state — the + * Angular equivalent of the React `FMGroup` wrapper. + * + * Place the controls you want to keep in sync as **direct children** of + * `` and give the group a `state`. The group passes that state + * into each control child that doesn't already have its own `state` (a + * child's own `state` always wins). Each child registers and reads the state + * through the same `@flexmonster/js` instance that creates its control, so + * there is no registry mismatch and the library's `` custom element + * is not needed. Controls that share the same `state.id` are linked by the + * library and stay in sync. + * + * React clones each child element to inject the `state` prop; Angular cannot + * clone projected content, so the group instead grabs its control children + * via `@ContentChildren` and sets their `state` input directly. The result is + * the same: a thin wrapper that propagates one state to its children, leaving + * the control components untouched. + * + * The group renders its children unchanged (just `` — no extra + * wrapper element). Only direct children are handled. + * + * @example + * + * + * + * + */ +@Component({ + selector: 'ngx-fm-group', + standalone: true, + template: '', +}) +export class FMGroup implements AfterContentInit { + @Input() state: StateInputParams | undefined; + + @ContentChildren(FMFlexmonster) private composites!: QueryList; + @ContentChildren(FMPivotTable) private pivotTables!: QueryList; + @ContentChildren(FMPivotFieldList) private pivotFieldLists!: QueryList; + @ContentChildren(FMFlatTable) private flatTables!: QueryList; + @ContentChildren(FMFlatFieldList) private flatFieldLists!: QueryList; + @ContentChildren(FMToolbar) private toolbars!: QueryList; + @ContentChildren(FMFilter) private filters!: QueryList; + + ngAfterContentInit(): void { + const controls: FMControl[] = [ + ...this.composites, + ...this.pivotTables, + ...this.pivotFieldLists, + ...this.flatTables, + ...this.flatFieldLists, + ...this.toolbars, + ...this.filters, + ]; + // A child's own state wins; otherwise inject the group's state. + for (const control of controls) { + if (control.state === undefined) { + control.state = this.state; + } + } + } +} diff --git a/projects/flexmonster/angular/ssr/public-api.ts b/projects/flexmonster/angular/ssr/public-api.ts index a32eebf..719fb5d 100644 --- a/projects/flexmonster/angular/ssr/public-api.ts +++ b/projects/flexmonster/angular/ssr/public-api.ts @@ -6,3 +6,4 @@ export * from './pivot-table.component'; export * from './pivot-field-list.component'; export * from './toolbar.component'; export * from './filter.component'; +export * from './group.component'; diff --git a/projects/test-app/src/app/app.html b/projects/test-app/src/app/app.html index 8edecec..e2f3249 100644 --- a/projects/test-app/src/app/app.html +++ b/projects/test-app/src/app/app.html @@ -24,6 +24,12 @@

Pivot

+

Group (shared state)

+ + + + +

Toolkit elements use example

Custom Button diff --git a/projects/test-app/src/app/app.ts b/projects/test-app/src/app/app.ts index 58c0328..80602fb 100644 --- a/projects/test-app/src/app/app.ts +++ b/projects/test-app/src/app/app.ts @@ -2,7 +2,7 @@ import { AfterViewInit, Component, CUSTOM_ELEMENTS_SCHEMA, signal, viewChild } f import { RouterOutlet } from '@angular/router'; //Import Flexmonster Angular SSR components -import { FMFlexmonster, FMFlatFieldList, FMFlatTable, FMPivotFieldList, FMPivotTable, FMToolbar } from '@flexmonster/angular/ssr'; +import { FMFlexmonster, FMFlatFieldList, FMFlatTable, FMPivotFieldList, FMPivotTable, FMToolbar, FMGroup } from '@flexmonster/angular/ssr'; //Import Flexmonster Angular CSR components // import { FMFlexmonster, FMFlatTable, FMPivotTable, FMToolbar, FMFlatFieldList, FMPivotFieldList } from '@flexmonster/angular'; @@ -18,7 +18,7 @@ import { FMCompositeViewType, IFMFlexmonsterOptionsInputParams, StateInputParams @Component({ selector: 'app-root', - imports: [FMFlexmonster, FMFlatFieldList, FMFlatTable, FMPivotFieldList, FMPivotTable, FMToolbar, ToolbarComponent], + imports: [FMFlexmonster, FMFlatFieldList, FMFlatTable, FMPivotFieldList, FMPivotTable, FMToolbar, FMGroup, ToolbarComponent], templateUrl: './app.html', //Allow FM custom elements schemas: [CUSTOM_ELEMENTS_SCHEMA], @@ -183,4 +183,48 @@ export class App implements AfterViewInit { ] } }; + + //#region flexmonster group (shared state) + // A single state shared by every control inside . + // The pivot table and field list below inherit this state and stay in sync. + public stateGroup: StateInputParams = + { + "id": "state-group", + "dataset": { + "dataSource": { + "data": [{ + "Year": 2021, + "Gender": "Male", + "Name": "Liam", + "Count": 20000, + "State": "CA" + }], + "type": "json" + } + }, + "slice": { + "rows": [ + { + "fieldName": "Year" + }, + { + "fieldName": "Gender" + }, + { + "fieldName": "Name" + } + ], + "values": [ + { + "fieldName": "Count", + "aggregation": "sum" + } + ], + "columns": [ + { + "fieldName": "State" + } + ] + } + }; }