Angular ngrx ngrx/effects Redux
▌Introduction
What
is ngrx/effects?
ngrx/store is used
for the state management with reducer,
which is supposed to be pure function. So how can we do async or logical calls
with side effect in ngrx?
Why
ngrx/effects?
There are several benefits of it.
1. Decoupling
between state management and component.
In the following sample, we will keep writing the Shopping cart application.
So far we can put products into the shopping cart, and we are going to create
an order and save it to firebase.
▋Source code
▋Related articles
[Angular] Redux with ngrx/store –
Shopping cart sample (2)
[Angular] Redux with ngrx/effects – Shopping cart sample (3)
[Angular] Redux with ngrx/effects – Shopping cart sample (3)
▌Environment
▋Angular
5.2.0
▋@ngrx/store 5.2.0
▋@ngrx/effects 5.2.0
▌Implement
▋Current output
Our goal is to implement the “Send Order” event.
▋Create Reducer (with
ngrx/store)
▋IOrder.ts
import {
ShopItem } from '../class/ShopItem';
export interface IOrder
{
id: string;
customer: string;
status: string;
date:
string;
items: ShopItem[];
}
▋Order.ts
import {
IOrder } from '../interface/IOrder';
import {
ShopItem } from '../class/ShopItem';
export class Order implements IOrder{
id: string;
customer: string;
status: string;
date: string;
items: ShopItem[];
constructor() {
this.date = new
Date().toLocaleDateString();
this.items =
[];
}
}
▋OrderAction.ts
import {
Action } from "@ngrx/store";
import { Order
} from "./Order";
export class
OrderAction implements Action {
constructor(
public type:
string,
public
payload: Order) {
}
}
▋order.action.ts
An order has the status such as “save”, “saved”,
“cancel” …
Our goal in this sample is letting the user
save their order with items in the shopping cart. That means we will only use “save”
and “saved” for example.
export const SAVE = 'SAVE';
export const SAVED =
'SAVED';
export const CANCEL
= 'CANCEL';
export const
CANCELLED = 'CANCELLED';
export const
COMPLETE = 'COMPLETE';
export function
orderReducer(state: Order = new Order(), action:
OrderAction){
switch (action.type) {
case SAVE:
state = action.payload;
console.log("Order's
state : " + state.status);
return state;
case SAVED:
state = action.payload;
console.log("Order's
state : " + state.status);
return state;
case CANCEL:
return state;
case
CANCELLED:
return state;
case
COMPLETE:
state = action.payload;
return state;
default:
return state;
}
}
Don’t forget to add the new reducer into StoreModule.
▋app.module.ts
let
reducers: IStore = {
shopcart: shopcartReducer,
order: orderReducer
}
@NgModule({
imports: [
StoreModule.forRoot(reducers),
],
})
export class
AppModule { }
▋Component
▋shopcart.component.html
<button class='btn
btn-success' (click)="sendOrder()">
▋shopcart.component.ts
export class
ShopcartComponent implements OnInit {
private shopcart$:
Observable<IShopCart>;
private order$:
Observable<IOrder>;
private states: string[] =
[];
constructor(
private store:
Store<IStore>,
) {
//Get the reducer
this.shopcart$
= this.store.select<IShopCart>(x =>
x.shopcart);
this.order$
= this.store.select<IOrder>(x =>
x.order);
}
private sendOrder() {
this.shopcart$.subscribe(data
=> {
let date = new Date();
let
orderItem: Order = {
id: AppUtility.generateUUID(),
customer: this.loginUser.email,
status: SAVE,
date:
date.toLocaleDateString().concat(' ',
date.toLocaleTimeString()),
items: data.items
};
this.store.dispatch({
type: SAVE, payload: orderItem });
//Output states
this.order$.subscribe(ord
=> {
this.states.push(ord.status);
});
});
}
}
As we click the “Send Order” button, we will
get the following result now.
The state is “Saving” so far. We will use ngrx/effects to save the order and update the state to “Saved”.
▋Create async calls
First we have to create a service which
includes async calls.
▋order.service.ts
@Injectable()
export class
OrderService {
//Create new product
public create(ord: Order)
:Promise<void> {
//Save an order…
}
}
▋Side effect by using
ngrx/effects
▋order.effects.ts
import {
Effect, Actions } from "@ngrx/effects";
import { SAVE,
SAVED, CANCEL, CANCELLED, COMPLETE } from './order.action';
import {
OrderService } from '../service/order.service';
@Injectable()
export class
orderEffects {
constructor(
private
action$: Actions,
private
orderService: OrderService
) { }
@Effect() save$ = this.action$
.ofType(SAVE)
.switchMap((action) => {
let oa = <OrderAction>action;
oa.payload.id = AppUtility.generateUUID();
oa.payload.status = SAVED;
//Save the order to backend, database ...etc Or get something
let create$ = Observable.fromPromise(this.orderService.create(payload));
return create$.delay(1000).switchMap(() => {
return Observable.of({ 'type': SAVED, 'payload': oa.payload });
});
});
@Effect() saved$ = this.action$
.ofType(SAVED).delay(1000)
.switchMap((action) => {
let oa = <OrderAction>action;
oa.payload.status = COMPLETE;
return Observable.of({ 'type': COMPLETE, 'payload': oa.payload });
});
}
Here some key points,
n
@Effect() save$ = this.action$.ofType(SAVE).switchMap(…)
This effect will be triggered and translate the dispatched event with type: SAVE, into an observable.
PS. If you are not familiar with rxjs: switchMap, have a look at this article.
PS. If you are not familiar with rxjs: switchMap, have a look at this article.
n What should be returned in effects?
The next state (as Observable)! DO NOT return the current state, or the returned observable will trigger the same effect again and result in infinite loop!
The next state (as Observable)! DO NOT return the current state, or the returned observable will trigger the same effect again and result in infinite loop!
When we return the next state, it will trigger the mapping effect.
At last, import it to Effects Module.
▋app.module.ts
import {
orderEffects } from './ngrx/order.effects';
import {
EffectsModule } from '@ngrx/effects';
@NgModule({
imports: [
EffectsModule.forRoot([orderEffects]),
],
})
export class
AppModule { }
Remember that we logged the latest state in
reducer?
And this is the result after
sending an order:
We called the service in Effects: save$, to
create an order into the database, here is the data saved to RTDB in Firebase.
▋Demo
▌Sample codes
▌Reference