Search code examples
javascriptangularngxsangular-state-managmement

Angular NGXS don't recalculate selectors unless the selected data is updated in state


I have a NGXS state defined as below

export interface Project{
  _id:string,
  name:string
}

export class ProjectStateModel {
    projects: Project[];
}

@State<ProjectStateModel>({
    name: 'Projects',
    defaults: {
        projects: [{_id:'111', name:'Project 1'}, {_id:'222', name: 'Project 2'}]
    }
})
@Selector()
    static getAll(state: ProjectStateModel) {
        return state.projects;
    }

    @Selector()
    static getById(state: ProjectStateModel) {
        return (id) => {
            return state.projects.find(p => p._id === id);
        };
    }

    @Action(ProjectsAction.Add)
    add({ getState, patchState }: StateContext<ProjectStateModel>, { payload }: ProjectsAction.Add) 
    {
        const state = getState();
        patchState({
            projects: [...state.projects, ...payload]
        })
    }

Its a pretty straight forward state implementation. The state has 2 projects by default; 2 selectors, one to get all projects and other to get by id and an action to add a new project.

I am using the state in one of the components as shown below

export class ProjectDetailComponent{
    selectedPROJ: Project;
    selectedPROJ$: Observable<Project>;
    
    constructor(){
        this.selectedPROJ$ = this.store.select(ProjectState.getById)
                .pipe(map(filterFn => filterFn('111')));
        this.test = this.selectedPROJ$.subscribe(p => {
          console.log(p); //this get logged every time there is a change in state even though the project 111 hasn't changed
        })
    }
}

The issue I am facing is every time I update the state(say like add a new project to state), the selector for get by id re-triggers even though the selected item from the state hasn't changed.

For example in the code shown above I have selected project with id 111. If I dispatch an Add action to add a new project the selector getById re-triggers.

Is there any way in NGSX to define a parameterized selector which only gets triggered only if the selected item changes in the state?

I have looked at injectContainerState in NGXS docs but dont understand how to use that with this parametrized selector.


Solution

  • The selector will be memoized based on its selectors.

    @Selector() // <-- no selector injected 
    static getById(state: ProjectStateModel)
    

    Since the selector above doesn't have any reference to a state token, state class, or other selector, then by default the selector will run every time it's state changes. The power of selectors is that you can build on them to create fine grained selectors.

    Another option is to use dynamic selectors. Basically create the selector when you need it and pass in the arguments you need. Here is an example:

    https://stackblitz.com/edit/ngxs-selectors-upgky4?file=src/app/app.component.ts

    The example doesn't show best practices, and is really rough. For example you can't change the id. But it does show you another way of achieving what you want.

    Fantastic resource: https://www.youtube.com/watch?v=y3F99IsnNvI