@daffodil/cart
provides a fully featured state library to streamline the management of an application's state as well as driver interaction.
The facade is an abstraction that provides all the functionality needed for standard use. It's the recommended way to interact with the Daffodil state layer.
DaffCartStateModule
in the root component.StoreModule.forRoot({})
. This will be relevant later on when using the redux and state management features of @daffodil/cart
.@ngModule({
imports:[
StoreModule.forRoot({}),
DaffCartStateModule
]
})
export class AppModule {}
The DaffCartStateModule
provides a DaffCartFacade
that wraps the complexities of the state library into one place. This facade will handle updating the user's cart and can also be used to build UI with behaviors common to a cart.
To inject the facade inside a component, include an instance of DaffCartFacade
in the component's constructor.
@Component({})
export class CartComponent {
constructor(public cartFacade: DaffCartFacade) {}
}
Once the DaffCartFacade
has been set up in the component, it can now be used to manage a user's cart. To perform operations on the cart, pass actions to the DaffCartFacade#dispatch
method. When carts are created using the DaffCartCreate
action, Daffodil will save the cart ID in local storage and automatically pass it to the driver layer for future operations. Various cart properties and a list of errors are available on the cart facade as observable streams.
Note: The storage mechanism can be configured. See the storage guide for more details.
Additionally, the Daffodil cart facade provides three different loading states for each section of the cart:
State | Description |
---|---|
mutating$ |
Tracks when an update to a cart property is occurring |
resolving$ |
Tracks when new data is being fetched but no updates are taking place |
loading$ |
Emits true when either mutating$ or resolving$ is true |
There is also overall featureLoading$
, featureMutating$
, and featureResolving$
streams to track loading for any section of the cart. These can be used to enhance the application's UI.
The following example illustrates a component used to display and manage a cart's items.
import {
DaffCartItemAdd,
DaffCartItemList,
DaffCartItemInput,
DaffCartItemInputType,
DaffCartFacade,
DaffCartItem
} from '@daffodil/cart';
export class CartItemComponent implements OnInit {
items$: Observable<DaffCartItem[]>;
errors$: Observable<string[]>;
loading$: Observable<boolean>;
constructor(public cartFacade: DaffCartFacade) {}
ngOnInit() {
this.items$ = this.cartFacade.items$;
this.errors$ = this.cartFacade.itemErrors$;
this.loading$ = this.cartFacade.itemLoading$;
// load the cart items
this.cartFacade.dispatch(new DaffCartItemList())
}
addSimpleItem(productId: string, qty: number) {
const input: DaffCartItemInput = {
type: DaffCartItemInputType.Simple,
productId,
qty
};
this.cartFacade.dispatch(new DaffCartItemAdd(input));
}
}
In this example, three observable streams are assigned from
cartFacade
. Then whenaddSimpleItem
is called, thecartFacade
'sdispatch
function is called with the appropriately formed input. The input data is then sent off to the backend and the three observable streams are updated when a response is received.
Accessing state with the facade is recommended but for greater flexibility the redux store's state can be directly accessed with Daffodil's selectors.
To properly maintain memoization of selectors that are compatible with generic models, Daffodil selectors are hidden behind an exported getter function. This function returns an object that contains the selectors.
The following example also showcases a component used to display and manage a cart's items but without using the facade.
import {
DaffCartItemAdd,
DaffCartItemList,
DaffCartItemInput,
DaffCartItemInputType,
DaffCartItem,
getDaffCartSelectors
} from '@daffodil/cart';
export class CartItemComponent implements OnInit {
items$: Observable<DaffCartItem[]>;
errors$: Observable<string[]>;
loading$: Observable<boolean>;
constructor(public store: Store<any>) {}
ngOnInit() {
const {
selectCartItems,
selectItemErrors,
selectItemLoading
} = getDaffCartSelectors();
this.items$ = this.store.pipe(select(selectCartItems));
this.errors$ = this.store.pipe(select(selectItemErrors));
this.loading$ = this.store.pipe(select(selectItemLoading));
// load the cart items
this.store.dispatch(new DaffCartItemList())
}
addSimpleItem(productId: string, qty: number) {
const input: DaffCartItemInput = {
type: DaffCartItemInputType.Simple,
productId,
qty
};
this.store.dispatch(new DaffCartItemAdd(input));
}
}
This tutorial will walk you through the cart resolution process, which is responsible for resolving a user's cart upon application initialization. This behavior is chiefly managed by the DaffResolvedCartGuard
.
At the moment, the following scenarios are handled by the DaffResolvedCartGuard
.
For customer cart support, use @daffodil/cart-customer.
Assuming that you're already using the DaffCartStateModule
and have previously selected a driver for @daffodil/cart
, you can simply add the guard to your route's canActivate
and the guard will handle the rest.
import { Routes, RouterModule } from '@angular/router';
import { HelloComponent } from './hello.component';
import { DaffResolvedCartGuard } from '@daffodil/cart/state';
import { DaffCartInMemoryDriverModule } from '@daffodil/cart/driver/in-memory';
export const appRoutes: [
{
path: '',
component: HelloComponent,
canActivate: [DaffResolvedCartGuard],
}
]
@NgModule({
imports: [
//DaffCartInMemoryDriverModule.forRoot(),
DaffCartStateModule.forRoot(),
RouterModule.forRoot(appRoutes),
],
exports: [RouterModule],
})
export class AppModule {}
Upon adding the code, load up the route and take a look at the Network Requests in your browser. You should see at least one request to your ecommerce systems's cart endpoint that attempts resolution.
You can configure the route to which the DaffResolvedCartGuard
navigates when an error occurs during resolution. See the configuration guide and the resolution
key of DaffCartStateConfiguration
for more information.
The guard's return is observable, and as such, when paired with other guards it won't necessarily complete in the order you expect, be sure to be careful about your guard ordering.
For example, Angular provides no guarantee that either of these guards runs before the other.
{
...,
canActivate: [DaffResolvedCartGuard, SomeOtherGuard]
}
If you need the guarantee, you can nest the guards.
{
...,
canActivate: [DaffResolvedCartGuard],
children: [
{
...
canActivate: [SomeOtherGuard]
}
]
}
@daffodil/cart/state
exposes a forRoot
method on the DaffCartStateModule
that allows you to pass in a configuration object to configure the behavior of the package.
You can import it like so:
import { Routes, RouterModule } from '@angular/router';
import { HelloComponent } from './hello.component';
import { DaffResolvedCartGuard } from '@daffodil/cart/state';
import { DaffCartInMemoryDriverModule } from '@daffodil/cart/driver/in-memory';
export const appRoutes: [
{
path: '',
component: HelloComponent,
canActivate: [DaffResolvedCartGuard],
}
]
@NgModule({
imports: [
DaffCartInMemoryDriverModule.forRoot(),
DaffCartStateModule.forRoot(),
RouterModule.forRoot(appRoutes),
],
exports: [RouterModule],
})
export class AppModule {}
The only argument to forRoot
is the configuration object. For more information, see DaffCartStateConfiguration
.
@daffodil/cart/state
provides mechanisms for consuming applications and libraries to modify state reduction behavior. Injected reducers run after the Daffodil reducers and should take care to not violate the DaffCartReducersState
interface.
@daffodil/cart/state
consumers may wish to modify the state for the daffCart
feature in a way not explicitly provided by @daffodil/cart/state
. A common example is adding payment info to the cart payment object in response to non-@daffodil/cart/state
actions.
The following example demonstrates modifying the cart's payment info in response to a user-defined action.
interface MyPaymentInfo {
ccLast4: string;
}
class MyUpdatePaymentSuccessAction implements Action {
type = 'My Update Payment Success Action';
constructor(
public paymentInfo: MyPaymentInfo,
) {}
}
function myPaymentUpdateReducer(state: DaffCartReducersState, action: MyUpdatePaymentSuccessAction): DaffCartReducersState {
switch (action.type) {
case 'My Update Payment Success Action':
return {
...state,
cart: {
...state.cart,
cart: {
...state.cart.cart,
payment: {
...state.cart.cart.payment,
payment_info: {
...state.cart.cart.payment.payment_info, // careful not to overwrite existing payment data
...action.paymentInfo,
},
},
},
},
};
default:
return state;
}
}
@NgModule({
providers: [
...daffCartProvideExtraReducers(
myPaymentUpdateReducer,
),
],
})
class MyPaymentModule {}