I will use ngrx/store to implement a shopping cart example from my previous articles,
Before we implement the shopping cart, the application currently is looked like this,
Let’s create a component for booking a single product like this,
export class ShopItem {
count: number; //Counter
price: number; //Cash summary
}
import { Component, OnInit, Input, Output } from '@angular/core';
import { Product } from '../../../class/Product';
import { ShopItem } from '../../../class/ShopItem';
@Component({
selector: 'product-booking',
template: `
<table>
<tr>
<td (click)="decrement()"><label style="min-width:25px"><i class="fa fa-minus"></i></label></td>
<td><input style="max-width:50px" readonly="readonly" type="text" value="{{shopItem.count}}"/></td>
<td (click)="increment()"><label style="min-width:25px"><i class="fa fa-plus"></i></label></td>
</tr>
</table>
`
})
export class ProductBookingComponent implements OnInit {
@Input('price') price: number;
private shopItem: ShopItem = null;
constructor() {
this.shopItem = new ShopItem({ 'count': 0, 'price': this.price });
}
private increment() {
this.shopItem.count += 1;
}
private decrement() {
this.shopItem.count -= 1;
}
}
Then put the ProductBookingComponent
into all three product components' HTML:
Take ProductBooksComponent
for example,
<div>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th class="text-center">ID</th>
<th class="text-center">Title</th>
<th class="text-center">Price</th>
<th>Shopping Cart</th>
<th></th>
</tr>
</thead>
<tr *ngFor="let prod of books">
<td>{{prod.Id}}</td>
<td>{{prod.Title}}</td>
<td>{{prod.Price}}</td>
<td><product-booking [price]="prod.Price"></product-booking></td>
<td>
<input type="button" value="Edit" class="btn btn-info" (click)="edit(prod)" />
<input type="button" value="Remove" class="btn btn-danger" (click)="remove(prod)" />
</td>
</tr>
</table>
</div>
Result :
We would like to have a shopping cart, which can stores how many items we orders and how much totally cost.
Yes! We can use Redux
pattern here to stores the shopping cart state.
Furthermore, while the state (shopping cart) changes by the ProductBookingComponent
, emit the current state (shopping cart) to parent components.
First, create a State interface/class.
export interface IShopCart {
cnt: number; //total booking counter
sum: number; //total cost
}
import { IShopCart } from '../interface/IShopCart';
export class ShopCart implements IShopCart {
cnt: number;
sum: number;
constructor() {
this.cnt = 0;
this.sum = 0;
}
}
import { Action, ActionReducer } from '@ngrx/store';
import { IShopCart } from '../interface/IShopCart';
import { ShopCart } from '../class/ShopCart';
import { ShopItem } from '../class/ShopItem';
export const PUSH = 'PUSH';
export const PULL = 'PULL';
export const CLEAR = 'CLEAR';
export const shopcartReducer: ActionReducer<IShopCart> = (state: ShopCart = new ShopCart(), action: Action) => {
switch (action.type) {
case PUSH:
return pushToCart(state, action.payload);
case PULL:
return pullFromCart(state, action.payload);
case CLEAR:
state.cnt = 0;
state.sum = 0;
return state;
default:
return state;
}
}
export function pushToCart(shopcart: ShopCart, payload: ShopItem) {
shopcart.cnt += 1;
shopcart.sum += payload.price * 1;
return shopcart;
}
export function pullFromCart(shopcart: ShopCart, payload: ShopItem) {
shopcart.cnt -= 1;
shopcart.sum -= payload.price * 1;
return shopcart;
}
Notice that,
- The state is a
ShopCart
object.- The reducer needs to know who/what is updating the state thru the information in
action payloads
.Import the reducer to injector
Use StoreModule.provideStore(shopcart: shopcartReducer)
or if you have multiple reducers, import them like this,
//skip...
import { ProductBookingComponent } from './product-booking.component';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from '../../../service/counter.action';
import { shopcartReducer } from '../../../service/shopcart.action';
let rootReducer: any = {
counter: counterReducer,
shopcart: shopcartReducer
}
@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
ProductRoutes,
StoreModule.provideStore(rootReducer)
],
declarations: [
//skip...
ProductBookingComponent
],
providers: [
//skip...
],
bootstrap: [AppComponent]
})
export class AppModule { }
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Store } from '@ngrx/store';
@Component({
selector: 'product-booking',
template: `
<table>
<tr>
<td (click)="decrement()"><label style="min-width:25px"><i class="fa fa-minus"></i></label></td>
<td><input style="max-width:50px" readonly="readonly" type="text" value="{{shopItem.count}}"/></td>
<td (click)="increment()"><label style="min-width:25px"><i class="fa fa-plus"></i></label></td>
</tr>
</table>
`
})
export class ProductBookingComponent implements OnInit {
@Input('price') price: number;
@Output('emit-events') emitEvents = new EventEmitter<ShopCart>(true);
private shopItem: ShopItem = null;
private shopcart: Observable<IShopCart>;
constructor(private store: Store<IShopCart>) {
this.shopItem = new ShopItem({ 'count': 0, 'price': this.price });
this.shopcart = store.select("shopcart");
}
private increment() {
this.shopItem.count += 1;
this.store.dispatch({ type: PUSH, payload: this.shopItem });
this.shopcart.subscribe(data => {
this.emitEvents.emit(data);
});
}
private decrement() {
this.shopItem.count -= 1;
this.store.dispatch({ type: PULL, payload: this.shopItem });
this.shopcart.subscribe(data => {
this.emitEvents.emit(data);
});
}
}
<!-- skip... -->
<product-booking [price]="prod.Price" (emit-events)="setShopCart($event)"></product-booking>
<!-- skip... -->
//skip...
private setShopCart(data: ShopCart) {
this.toastr.info(data.cnt + ' items, total $' + data.sum, 'Shopping Cart', this.toastrOptions);
}
You may find that although we store the total counts and prices in the state (shopping cart), we does not have the information of individual product in the state.
We will fix it and complete the sample in the next day :)