Component Store — Series 3

Preview — 3 of 10 questions

heroCount$ is read by three different template bindings. Is ComponentStore's select() just a `BehaviorSubject` wrapper, re-running its projection function and re-emitting on every single state change regardless of whether the derived value actually changed?

javascript
export class HeroStore extends ComponentStore<HeroState> {
  readonly heroCount$ = this.select(state => state.heroes.length);
}
ANo — ComponentStore's selectors apply the same kind of change-detection (comparing the newly-derived value against the previous one, by default with reference/shallow equality) that global @ngrx/store selectors do. If an unrelated part of HeroState changes but state.heroes.length computes to the same number as before, heroCount$ does not re-emit — subscribers are only notified when the actually-derived value changes
BYes — ComponentStore.select() has no memoization; every state update re-emits through every selector unconditionally
CIt memoizes, but only if { debounce: true } is explicitly passed to select()
DMemoization only applies to selectors combining multiple sources with select(source1$, source2$, project), not to a single-source selector like this one

What does @ngrx/router-store's getSelectors() provide here, and why combine it with selectHeroEntities this way?

javascript
export const selectRouteParams = getSelectors(selectRouterState).selectRouteParams;

export const selectSelectedHero = createSelector(
  selectRouteParams,
  selectHeroEntities,
  (params, heroes) => heroes[params['id']],
);
A@ngrx/router-store syncs the router's own state (current URL, params, query params, route data) into the NgRx state tree as just another feature slice, alongside everything else. getSelectors() generates the standard selectors (selectRouteParams, selectQueryParams, selectRouteData, and others) for reading that slice — letting route information be combined with any other piece of app state through the exact same createSelector composition used everywhere else, rather than needing components to separately inject ActivatedRoute just to combine it with store data
BgetSelectors() reads route params directly from ActivatedRoute, entirely bypassing the NgRx store
CThis pattern only works for routes with static, non-dynamic path segments
DgetSelectors() requires the router itself to be configured with withComponentInputBinding() to function

What's the actual trade-off between these two common ways of testing the same effect?

javascript
// Style A — TestScheduler marbles
testScheduler.run(({ hot, cold, expectObservable }) => {
  actions$ = hot('-a', { a: loadHero({ id: '1' }) });
  const response = cold('--b', { b: mockHero });
  heroService.getHero.and.returnValue(response);
  expectObservable(effects.loadHero$).toBe('---c', { c: loadHeroSuccess({ hero: mockHero }) });
});

// Style B — provideMockActions
actions$ = of(loadHero({ id: '1' }));
TestBed.overrideProvider(Actions, { useValue: actions$ });
effects.loadHero$.subscribe(result => expect(result).toEqual(loadHeroSuccess({ hero: mockHero })));
AStyle B is strictly obsolete; only marble-based testing is considered valid for NgRx effects
BThere is no real difference; both styles test exactly the same thing with identical guarantees
CStyle A (marbles) can assert on the precise timing of emissions relative to each other — useful when an effect's behavior around debouncing, delays, or overlapping requests (switchMap cancellation, say) is exactly what's being verified. Style B (provideMockActions/plain Observables) is simpler to write and read for the common case of "given this action, does the effect eventually produce that action," when the precise timing isn't actually part of what's being tested
DStyle A can only test effects that dispatch, never { dispatch: false } effects

Sign up free to play

Answer all 10 questions (7 more), see explanations for every answer, and track your score.