This project was generated with Angular CLI version 11.0.6.
Reducers in NgRx are responsible for handling transitions from one state to the next state in your application. Reducer functions handle these transitions by determining which actions to handle based on the action's type.
Reducer takes 2 arguments - Current State, Action
(state = initialState) is the Default State for the Reducer
NgRX automatically passes current State, action Invoked to the Reducer. Reducer only has synchronous code.
export function shoppingListReducer(state = initialState, action: ShoppingListAction) {
switch(action.type) {
case 'ADD_INGREDIENT':
state.ingredients.push();
}
}
We should not directly modify the state like above using push, pop, etc. Instead return a new Object by copying the properties of old State.. ...state copies the old state. We can attach the property we want to modify in 2nd param.
case ADD_INGREDIENT:
return {
...state, //Always copy the old state
<!-- Copy all the previous ingredients -->
ingredients: [...state.ingredients, action.payload]
}
Action is an Object which can be created using the Action Class created for each type.
export const ADD_INGREDIENT = 'ADD_INGREDIENT';
export class AddIngredient implements Action {
readonly type = ADD_INGREDIENT;
payload: Ingredient;
}
- type => Identifier of action
- readonly => TS feature. Errors when tried to modify
- payload => Data that is passed to Reducer
Note on Actions: Any action that we dispatch reaches all reducers.
StoreModule.forRoot() takes in a Action-ReducerMap which is a JS Object.
JS Object within forRoot consists of an Identifier as key and Reducer Function as value.
StoreModule.forRoot({
shoppingList: ShoppingListReducer
})
NgRX allows us to inject Store within Components and Services to access Current State of the Application.
shopping-list.component.ts
import { store } from '@ngrx/store';
constructor() {
private store: Store<{shoppingList: { ingredients: Ingredients[] }}>
}
Type of the Store is the ActionReducerMap we set up in AppModule. JS Object with key as Identifier for the data but the type of this Identifier is not the Reducer Function but the value this Reducer Function yields.
Injected Store Service provides a method select() which allows us to access a slice of our Store.
ingredients: Observable<{ingredients: Ingredient[]}>
this.ingredients = this.store.select('shoppingList');
The output of the above method is an Observable Object. Replace ingredients Array to Observable.
ngFor="let ingredient of (ingredients | async).ingredients"
Inject Store Service within component where we want to dispatch the Action. Use the Action Class name as argument
this.store.dispatch(new ShoppingListActions.AddIngredient(ingredient));
Action Dispatched -> Store finds appropriate reducer which has the dispatched action defined -> Reducer modifies current state based on Action payload -> Reducer returns new State.
export type groupActions = AddIngredients | UpdateIngredients | DeleteIngredients;
-
When our
app stategrows, we might have more than onereducerin ourstate. Whenever theAppStateis modified, we need to modify thestore definitionthroughout thecomponentswhere we haveInjectedthe Store service. This process is clumsy. -
Hence we export an
Interface, which describes how State looks like for thisReducer. Below is the Interface for state of ShoppingListReducer.
export interface State {
ingredients: Ingredient[];
editedIngredient: Ingredient;
editedIngredientIndex: number;
}
const initialState: State = {
ingredients: [new Ingredient('Apples', 5), new Ingredient('Tomatoes', 10)],
editedIngredient: null,
editedIngredientIndex: -1,
};
- We can also create an
Interfacefor theAppState- in future, we might have more than oneReducer.
export interface AppState {
shoppingList: State
}
shopping-list.component.ts
private store: Store<fromShoppingList.AppState>
Often times, we need to perform some logic after an action has been dispatched, and the store has been updated. Because reducers should be side-effect free, we need a way to handle these side-effects.
@ngrx offers a library called @ngrx/effects to solve these problems.
Effects is just a class where we inject actions just like reducer.
export class AuthEffects{
constructor(private actions$: Actions){}
}
actions$ is an Observable which emits the action that is currently dispatched.
Note: We don't need to subscribe to the actions$. NgRX does it itself.
Use ofType operator provided by ngrx/effects to filter the type of Action we need to apply this effect for.
@Effect()
authLogin = this.actions$.pipe(ofType(fromAuthActions.LOGIN_START));
Once we are done with the code in the effect, we should dispatch an action.
@Injectable()
export class AuthEffects {}
