Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When do selectors execute? ngxs

Tags:

ngxs

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?

like image 237
Сергей Avatar asked Feb 22 '19 06:02

Сергей


1 Answers

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.

like image 134
Mark Whitfeld Avatar answered Nov 14 '22 16:11

Mark Whitfeld