I’m working on a project that uses both Angular v16 and ngxs v18.0.
We have several different grids that all need the same variety of actions, so we decided to try making a factory function that would spit out action class definitions with unique type
properties. The function simply returns an anonymous object with a specific interface, where each property of the object is a class definition. The code works, but I cannot figure out how to get typescript to understand that the class definitions are simply properties of the object, rather than a declared namespace it should look for in the global scope.
Here’s the factory module code:
export interface GridActions {
Initialize: ActionDef,
Fetch: ActionDef,
FetchSuccess: ActionDef,
FetchFailed: ActionDef,
UpdatePagingParams: ActionDef,
ToggleRowExpanded: ActionDef,
ExpandRow: ActionDef,
CollapseRow: ActionDef,
ToggleExpandAll: ActionDef,
UpdateFilter: ActionDef,
UpdateSort: ActionDef
}
export class GridActionFactory {
cache: {[key:string]:GridActions} = {};
private static instance: GridActionFactory;
static getActions(gridName: string): GridActions {
if (!this.instance) {
this.instance = new GridActionFactory();
}
if (this.instance.cache[gridName] !== undefined) {
return this.instance.cache[gridName];
}
const newActionCollection = {
Initialize: class Initialize {
static readonly type = `[Example] Initialize ${gridName} Grid State`
},
Fetch: class Fetch {
static readonly type = `[Example] Fetch ${gridName} Grid`;
},
FetchSuccess: class FetchSuccess {
static readonly type = `[Example] Fetch ${gridName} Success`;
constructor(public payload: SomeSwaggerDTO) {}
},
FetchFailed: class FetchFailed {
static readonly type = `[Example] Fetch ${gridName} Failed`;
constructor(public error: Error) {}
},
UpdatePagingParams: class UpdatePagingParams {
static readonly type = `[Example] Update ${gridName} Paging Params`;
constructor(public pageEvent: PageEvent) {}
},
ToggleRowExpanded: class ToggleRowExpanded {
static readonly type = `[Example] Toggle ${gridName} Row`;
constructor(public transferRequestId: string) {}
},
ExpandRow: class ExpandRow {
static readonly type = `[Example] Expand ${gridName} Row`;
constructor(public transferRequestId: string) {}
},
CollapseRow: class CollapseRow {
static readonly type = `[Example] Collapse ${gridName} Row`;
constructor(public transferRequestId: string) {}
},
ToggleExpandAll: class ToggleExpandAll {
static readonly type = `[Example] Toggle ${gridName} Expand All`;
},
UpdateFilter: class UpdateFilter {
static readonly type = `[Example] Update ${gridName} Filter`;
constructor(public event: FilterChangeEvent) {}
},
UpdateSort: class UpdateSort {
static readonly type = `[Example] Update ${gridName} Sort`;
constructor(public sort: Sort) {}
}
};
this.instance.cache[gridName] = newActionCollection;
return newActionCollection;
}
}
And then we have an ExampleActions.ts
file with the following:
export const ExampleActions = {
ActionableGrid: GridActionFactory.getActions('actionable-grid'),
PendingGrid: GridActionFactory.getActions('pending-grid'),
HistoricalGrid: GridActionFactory.getActions('historical-grid')
}
And then the usage:
import { ExampleActions } from './example.actions';
const AtgActions = ExampleActions.ActionableGrid;
const HtgActions = ExampleActions.HistoricalGrid;
export const EXAMPLE_STATE_TOKEN = new StateToken<ExampleStateModel>('example');
@State({
name: EXAMPLE_STATE_TOKEN,
defaults: exampleStateModelDefaults,
})
@Injectable()
export class ExampleState {
constructor() { }
@Action(AtgActions.UpdatePagingParams)
updateActionableGridPagingParams(ctx: StateContext<ExampleStateModel>, action: AtgActions.UpdatePagingParams) {
ctx.setState(
produce(draft => {
draft.actionableGridComponent.pagingParams.pageIndex = action.pageEvent.pageIndex;
draft.actionableGridComponent.pagingParams.previousPageIndex = action.pageEvent.previousPageIndex;
draft.actionableGridComponent.pagingParams.pageSize = action.pageEvent.pageSize;
})
);
ctx.dispatch(new AtgActions.Fetch);
}
}
Using AtgActions.UpdatePagingParams
in the @Action
decorator works fine, because it’s an actual run-time reference to a class definition. But the typescript parser throws a compilation error on the action
param of the action handler, complaining that it can’t find the namespace “AtgActions”.