user4676340
user4676340

Reputation:

NgRx : dispatch one event from a store to another

I'm beginning to use @ngrx and I'm facing an issue to which I can't seem to find an answer.

I illustrated it in this stackblitz : when a player picks a race, the specialty should be set back to undefined.

At first I thought I could use the reducer, but it doesn't have knowledge of the store without injection.

How should I approach this issue ?
(Note : this is a reproductible example, I know I can put a second dispatch in my functions. What I want is to do it automatically)

Related code :

import { Component } from '@angular/core';
import { Store, Action, ActionReducerMap } from '@ngrx/store';

@Component({
  selector: 'my-app',
  template: 
`
<select #race (change)="dispatchRace(race.value)" placeholder="Select a race">
  <option value="Elf">Elf</option>  
  <option value="Orc">Orc</option>  
  <option value="Dwarf">Dwarf</option>
</select>

  <select #spec (change)="dispatchSpecialty(spec.value)" placeholder="Select a specialty">
  <option value="Warrior">Warrior</option>  
  <option value="Berzerkrer">Berzerkrer</option>  
  <option value="Healer">Healer</option>  
</select>

<p>
  Current race: {{ (currentRace | async) || 'None' }}
</p>
<p>
  Current Spec: {{ (currentSpecialty | async) || 'None' }}  
</p>  
`
})
export class AppComponent {

  currentRace = this.store.select('race');
  currentSpecialty = this.store.select('specialty');

  constructor(public store: Store<CharacterState>) { }

  dispatchRace(race) {
    this.store.dispatch(new SetRaceAction(race));
  }

  dispatchSpecialty(spec) {
    this.store.dispatch(new SetSpecialtyAction(spec));
  }
}


const SET_RACE = '[RACE] Set';
const SET_SPECIALTY = '[CLASS] Set';

export class SetRaceAction implements Action {
  readonly type = SET_RACE;
  constructor(public race: string) { }
}

export class SetSpecialtyAction implements Action {
  readonly type = SET_SPECIALTY;
  constructor(public specialty: string) { }
}

export function raceReducer(state: string = undefined, action: SetRaceAction): string {
  return action.race || state;
}

export function specialtyReducer(state: string = undefined, action: SetSpecialtyAction): string {
  return action.specialty || state;
}

export interface CharacterState {
  readonly race: string;
  readonly specialty: string;
}

Upvotes: 1

Views: 4267

Answers (3)

FussinHussin
FussinHussin

Reputation: 1814

I wanted to add one more solution, that I have done in my own implementations when I use flags in my store.

just change the flag in your state during the action you dispatch, for example:

import * as AppActions from './app.actions'; import * as handler from './app.models';

export function appReducer(
        state: handler.AppState = handler.initialAppState,
        action: AppActions.AppActions
    ) {
        switch(action.type) {
            case AppActions.SET_RACE_ACTION:
                return {
                    ...state,
                    race: action.payload
                    // set your flag in state
                    myFlag: undefined
                 }
       }

}

Upvotes: 0

FussinHussin
FussinHussin

Reputation: 1814

So if I understand correctly: you want to dispatch one action that triggers another?

  1. this is best done using NGRX effects. Your effects file will listen for the action, and dispatch both when the action is caught. (excuse the snippet, i was having formatting issues)

// make sure to import rxjs 6 correctly
import { map, switchMap, tap } from 'rxjs/operators';

@Effect()
twoActionEvent = this.actions$
    .ofType(appActions.FIRST_ACTION)
    .pipe(
        switchMap((action$: appActions.firstAction) => {
            return //what ever you need in the first action
        }),
        map((firstActionPayload) => {
            return {
               type: appActions.FIRST_ACTION_SUCCESS,
                payload: firstActionPayload
            }
         }),
         tap(() => {
             this.store.dispatch(new appActions.SecondAction(//payload))
         })
     );
constructor(private actions$: Actions,
            private store: Store<AppState>) {}

  1. another way to do this would be to listen for the action in your app, by subscribing to a property in your store, and when that property is true: dispatch the second action

Upvotes: 3

ibenjelloun
ibenjelloun

Reputation: 7733

If you add a console.log in the raceReducer you can see that all the actions are handled by all the reducers :

export function raceReducer(state: string = undefined, action: SetRaceAction): string {
  console.log(action);
  return action.race || state;
}

You just need to handle SetRaceAction and SetSpecialtyAction differently.

Edit of the stackblitz.

Upvotes: 3

Related Questions