When do selectors execute? ngxs
Asked Answered
A

1

8

live demo https://stackblitz.com/edit/angular-vw78jf

There is ToppingsStateModel in my ngxs state

export interface ToppingsStateModel {
  entities: { [ id: number ]: Topping };
  selectedToppings: number[];
}

One action change my entities list, another action change selectedToppings. In products.component I get list of toppings from selector

export class ToppingsState {
  constructor(private toppingsService: ToppingsService) {
  }

  @Selector()
  static entities(state: ToppingsStateModel) {
    console.log('getEntities', state.entities);
    return state.entities;
  }

  @Selector([ToppingsState.entities])
  static toppings(state: ToppingsStateModel, entities: {[id: number]: Topping}): Topping[] {
    return Object.keys(entities).map(id => entities[parseInt(id, 10)]);
  }
 ...
}

and it's product.component

export class ProductsComponent implements OnInit {

  @Select(ToppingsState.toppings) toppings$: Observable<Topping[]>;

  constructor(private store: Store, private actions$: Actions) {}

  ngOnInit() {
    const state = this.store.dispatch(new LoadToppings());
    setTimeout(() => this.store.dispatch(new VisualizeToppings([1])), 2000);
    this.toppings$.subscribe((toppings) => {console.log('UUUU NEW TOPPINGS!!!')});
  }
}

when I dispatch VisualizeToppings action I get new toppings value. In my console I have

action [Products] Load Toppings @ 10:57:59.735
getEntities {}
UUUU NEW TOPPINGS!!!
getEntities {1: {…}, 2: {…}}
UUUU NEW TOPPINGS!!!
action [Products] Visualize Toppings @ 10:58:01.744
getEntities {1: {…}, 2: {…}}
UUUU NEW TOPPINGS!!!

I changed another part of the state. Why did selectors execute again when I dispatched VisualizeToppings action? What do I do wrong?

Ardeha answered 22/2, 2019 at 6:18 Comment(3)
From memory I think custom selectors execute every time there is a state change - can't find this in the documentation, but I might've read it on NGXS github or in the slack channel. I don't think you've done anything 'wrong' to cause that to happen.Cromagnon
Out of interest if rather than a static selector, what happens if you subscribe direct? e.g. @Select(state => state.toppings.entities) toppings$ ..Cromagnon
Direct subscription works O_o But this is a bad way to solve the problem, my opinion. P.S. in NGRX everything works right, when I use createSelectorModena
D
8

There is a known issue with @Selector where the model of the container state class is always assumed as the first parameter. See: https://github.com/ngxs/store/issues/386#issuecomment-395780734

This is your problem... Because of this first parameter, your selector has a dependency on the state model ToppingsStateModel and the specified ToppingsState.entities selector.

  @Selector([ToppingsState.entities])
  static toppings(state: ToppingsStateModel, entities: {[id: number]: Topping}): Topping[] {
    return Object.keys(entities).map(id => entities[parseInt(id, 10)]);
  }

This results in the selector being recalculated when any part of the ToppingsStateModel changes. As a workaround you can move the selector to another class that is not a State class and remove the first parameter. I refer to these as Query classes. This is known as a meta-selector see the docs here: https://ngxs.gitbook.io/ngxs/concepts/select#meta-selectors

This will be fixed as part of the breaking changes in NGXS v4 (see https://github.com/ngxs/store/issues/827) and there is currently a PR for a feature flag to change this behaviour before NGXS v4 arrives. See: https://github.com/ngxs/store/pull/858

I hope this helps and explains the issue.

Dichromaticism answered 22/2, 2019 at 12:25 Comment(2)
Thanks for the official answer @Mark WhitieldCromagnon
Thanks, you've helped to understandModena

© 2022 - 2024 — McMap. All rights reserved.