Search code examples
ngxs

Guideline for memoized selectors in state base class


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.


Solution

  • 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