I have a question regarding NGXS Store and the usage of memoized @Selector()
s in a state class hierarchy.
What would be the recommended approach to the issue described below? The NGXS Store documentation does not provide a guideline/recommendation in this regard.
Given this sample state setup,
export interface BaseModel { data: string[]; }
// base class not a state!
export class BaseState {
@Selector()
static dataSelect(state: BaseModel) { return state.data; }
// ...
}
export interface DerivedModel extends BaseModel {}
@State<DerivedModel>({ name: 'derived'; })
export class DerivedState extends BaseState {
// ...
}
export interface UsingModel { thing: number; }
@State<UsingModel>({ name: 'using'; })
export class UsingState implements NgxsOnInit {
constructor(private _store: Store) {}
ngxsOnInit(ctx: StateContext<UsingModel>) {
// have this state update when derived state changes
this._store.select(DerivedState.dataSelect).subscribe(data => console.log(data));
}
// ...
}
When letting this piece of code run it will print out undefined
because the state
argument
of the dataSelect(...)
method will not be set.
I tracked the cause to BaseState
not being a NGXS State and therefore missing an internal NGXS_META
property which in turn causes the undefined
argument.
Adding BaseState
to the selector (such as @Selector([BaseState])
) to force the state to still be
included also does not have the desired effect, because now NGXS cannot navigate to a matching state slice.
I found two ways to make this work as desired:
1. duplicate the @Selector(...)
method in every derived state implementation. This though defeats the reasons why the state class hierarchy was originally designed.
2. use something like @DerivedSelector(...)
that works according to the standard decorator but dynamically creates selectors on use for each of the encountered derived state classes.
Thank you.
As far as I know this can not be achived using the @Selector
annotation, but with createSelector()
.
export class BaseState {
static dataSelect() {
return createSelector(
[this],
(state: BaseModel) => {
return state.data;
}
);
}
//...
}
If you change your base state like this, your code will work. For details refer to the NGXS docs