Skip to content
Open
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
3 changes: 3 additions & 0 deletions projects/flexmonster/angular/src/flexmonster.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -16,6 +17,7 @@ import { FMFilter } from './filter.component';
FMPivotFieldList,
FMToolbar,
FMFilter,
FMGroup,
],
exports: [
FMFlexmonster,
Expand All @@ -25,6 +27,7 @@ import { FMFilter } from './filter.component';
FMPivotFieldList,
FMToolbar,
FMFilter,
FMGroup,
]
})
export class FMModule{ }
75 changes: 75 additions & 0 deletions projects/flexmonster/angular/src/group.component.ts
Original file line number Diff line number Diff line change
@@ -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
* `<ngx-fm-group>` 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 `<fm-group>` 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 `<ng-content>` — no extra
* wrapper element). Only direct children are handled.
*
* @example
* <ngx-fm-group [state]="state">
* <ngx-fm-pivot-table></ngx-fm-pivot-table>
* <ngx-fm-pivot-field-list></ngx-fm-pivot-field-list>
* </ngx-fm-group>
*/
@Component({
selector: 'ngx-fm-group',
standalone: true,
template: '<ng-content></ng-content>',
})
export class FMGroup implements AfterContentInit {
@Input() state: StateInputParams | undefined;

@ContentChildren(FMFlexmonster) private composites!: QueryList<FMControl>;
@ContentChildren(FMPivotTable) private pivotTables!: QueryList<FMControl>;
@ContentChildren(FMPivotFieldList) private pivotFieldLists!: QueryList<FMControl>;
@ContentChildren(FMFlatTable) private flatTables!: QueryList<FMControl>;
@ContentChildren(FMFlatFieldList) private flatFieldLists!: QueryList<FMControl>;
@ContentChildren(FMToolbar) private toolbars!: QueryList<FMControl>;
@ContentChildren(FMFilter) private filters!: QueryList<FMControl>;

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;
}
}
}
}
1 change: 1 addition & 0 deletions projects/flexmonster/angular/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
75 changes: 75 additions & 0 deletions projects/flexmonster/angular/ssr/group.component.ts
Original file line number Diff line number Diff line change
@@ -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
* `<ngx-fm-group>` 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 `<fm-group>` 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 `<ng-content>` — no extra
* wrapper element). Only direct children are handled.
*
* @example
* <ngx-fm-group [state]="state">
* <ngx-fm-pivot-table></ngx-fm-pivot-table>
* <ngx-fm-pivot-field-list></ngx-fm-pivot-field-list>
* </ngx-fm-group>
*/
@Component({
selector: 'ngx-fm-group',
standalone: true,
template: '<ng-content></ng-content>',
})
export class FMGroup implements AfterContentInit {
@Input() state: StateInputParams | undefined;

@ContentChildren(FMFlexmonster) private composites!: QueryList<FMControl>;
@ContentChildren(FMPivotTable) private pivotTables!: QueryList<FMControl>;
@ContentChildren(FMPivotFieldList) private pivotFieldLists!: QueryList<FMControl>;
@ContentChildren(FMFlatTable) private flatTables!: QueryList<FMControl>;
@ContentChildren(FMFlatFieldList) private flatFieldLists!: QueryList<FMControl>;
@ContentChildren(FMToolbar) private toolbars!: QueryList<FMControl>;
@ContentChildren(FMFilter) private filters!: QueryList<FMControl>;

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;
}
}
}
}
1 change: 1 addition & 0 deletions projects/flexmonster/angular/ssr/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
6 changes: 6 additions & 0 deletions projects/test-app/src/app/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ <h2 id="pivot">Pivot</h2>
<ngx-fm-pivot-table #pivot [state]="stateFmPivot"></ngx-fm-pivot-table>
<ngx-fm-pivot-field-list #fieldListPivot [state]="stateFmPivot"></ngx-fm-pivot-field-list>

<h2 id="group">Group (shared state)</h2>
<ngx-fm-group [state]="stateGroup">
<ngx-fm-pivot-table></ngx-fm-pivot-table>
<ngx-fm-pivot-field-list></ngx-fm-pivot-field-list>
</ngx-fm-group>

<h2 id="toolkit">Toolkit elements use example</h2>
<fm-button size="sm" [disabled]="disabled">Custom Button</fm-button>
<fm-selectable-list data-provider="1,2,3"></fm-selectable-list>
48 changes: 46 additions & 2 deletions projects/test-app/src/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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],
Expand Down Expand Up @@ -183,4 +183,48 @@ export class App implements AfterViewInit {
]
}
};

//#region flexmonster group (shared state)
// A single state shared by every control inside <ngx-fm-group>.
// 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"
}
]
}
};
}