Search code examples
angulartypescripttypesstorengxs

How to have type-safety when using NGXS select?


I'm using NGXS for my state management, it's quite concise and practical. I've multiple store, almost one per module in my angular app.

The issue I've is that when I do select, I've to specify myself what is the type of the selected property. I had already some occurences where the store changed but not all the usage, and it was not possible to detect the issue before going on the page and having an error. Also it would help to be able to just ctrl+click on the property I'm selecting to check the model definition.

Here are some examples on how I select data:

//here instead of boolean, I could have put anything, it won't crash until I actually execute the code
this.loading = this.store.select<boolean>(state => state.organization.isLoading);


//same with snapshot selection:
this.store.selectSnapshot<Organization>(state => state.organization.selectedOrganization);

Is there a way to select stuff and have type safety? for both snapshot and normal selection?


Solution

  • The issue here is, that state is not typed correctly in the callback you pass to store.selectSnapshot. It is not possible for ngxs to infer a correct typing here as you could define arbitrary states.

    So one option you have, is to explicitly type state. Assuming your state's model is called OrganizationStateModel it would look like this:

    this.loading = this.store.select((state: { organization: OrganizationStateModel }) => state.organization.isLoading);
    

    The other option is to use Memoized Selectors. They allow you to specify the selectors inside your state and type them correctly. This also has the upside of caching selector results, so for more complex selector logic, it might even boost performance. Also you get reuseable selectors defined directly in your state. Your state definition would look something like this:

    export interface OrganizationStateModel {
      isLoading?: boolean;
      selectedOrganization?: Organization
    }
    
    @State<OrganizationStateModel>({
      name: 'organization',
      defaults: {}
    })
    @Injectable()
    export class OrganizationState {
      @Selector()
      static loading(state: OrganizationStateModel): boolean {
        return state.isLoading ?? false;
      }
    }
    

    And selecting from the state would look like this:

    this.loading = this.store.select(OrganizationState.loading);