Guideline for memoized selectors in state base class

Viewed 497

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.

1 Answers

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

Related