Сайт визитка на Angular. Модуль корзины.
В данной статье рассмотрим создание модуля корзины. В статье создадим feature state, а также добавим необходимые компоненты для отображения информации о товарах в корзине и блока с переходом к шагу оформления заказа.
Интерфейсы
Как и в случае с модулем товаров, начнем разработку с создания базовых интерфейсов, которые будут использоваться в приложении.
Для этого создадим новую библиотеку:
nx g lib cart/common
Создадим файл cart.interface.ts
:
Как видно из примера, корзина будет содержать только один интерфейс CartProduct
, который будет представлять собой 3 характеристики:
productId
— id товара;count
— количество товаров данного типа;size
— размер соответствующего товара.
Cart State
Создадим feature state
, который будет управлять логикой добавления и удаления товаров в корзину.
Создадим новую библиотеку:
nx g lib cart/state
Сгененирируем хранилище:
nx g @nrwl/angular:ngrx cart --module=libs/cart/state/src/lib/cart-state.module.ts
Изменим экшены в cart.actions.ts
:
Как и в случае с модулем товаров, реализована аналогичная техника восстановления данных.
init
— экшен, который запускается при создании хранилища;restore
— экшен, в который передается значение изlocalStorage
для восстановления состояния;addProduct
— экшен, который запускает эффект добавления товара в корзину;addProductSuccess
— экшен, который происходит в случае успешного добавления товара в корзину;addProductFailure
— экшен, который происходит в случае ошибки добавления товара в корзину;removeProduct
— экшен, который запускает эффект удаления товара из корзины;removeProductSuccess
— экшен, который происходит в случае успешного удаления товара из корзины;removeProductFailure
— экшен, который происходит в случае ошибки удаления товара из корзины;changeProduct
— экшен, который запускает эффект изменения товара в корзине;changeProductSuccess
— экшен, который происходит в случае успешного изменения товара в корзине;changeProductFailure
— экшен, который происходит в случае ошибки изменения товара в корзине.
Изменим cart.reducer.ts
:
Из занимательного, так как в CartProduct
нет ID
, то в качестве идентификатора используется пара productId_size
:
export function selectCartProductId(cartProduct: CartProduct): string {
return `product_${cartProduct.productId}_size_${cartProduct.size}`;
}
export const cartAdapter: EntityAdapter<CartProduct> = createEntityAdapter<CartProduct>({
selectId: selectCartProductId,
});
Поправим cart.selectors.ts
:
В селекторах обычный набор, который входят:
selectLoaded
— селектор, который возвращает признак инициализации state;selectCartProducts
— селектор, который возвращает список товаров в корзине;selectCartProductsEntities
— селектор, который возвращает словарь товаров в корзине;selectCount
— селектор, который считает количество товаров в корзине и отдает его значение.
Изменим cart.effects.ts
:
Если разобрать эффекты, то там можно увидеть базовый набор для CRUD
операций: добавление, изменение и удаление.
Закончим изменения в файле cart.facade.ts
, добавив в CartFacade
все селекторы и экшены для манипуляции товаров в корзине:
В фасаде присутствуют обертки над вызовом экшенов — addProduct
, changeProduct
, removeProduct
и clear
.
Также с помощью Actions созданы observable, которые позволяют подписаться на срабатывания событий:
addProductSuccess$ = this.actions$.pipe(
ofType(CartActions.addProductSuccess),
map(({ cartProduct }) => cartProduct)
);
addProductFailure$ = this.actions$.pipe(
ofType(CartActions.addProductFailure),
map(({ cartProduct, error }) => ({ cartProduct, error }))
);
В данном примере предоставляется подписка на события успешного/не успешного добавления товара в корзину.
CartStateModule
будет следующим:
Аналогично модулю товаров, изменим импорт:
export * from './lib/cart.facade';
export * from './lib/cart-state.module';
Доступа к модулю и фасаду достаточно для реализации приложения.
Cart UI
Реализацию компонентов корзины начнем с общих модулей, в частности создадим несколько пайпов для отображения информации о товаре в корзине.
Cart Pipes
Добавим библиотеку:
nx g lib cart/ui/pipes
Создадим несколько пайпов:
nx g p cart-total-price --project=cart-ui-pipes
nx g p cart-product-price --project=cart-ui-pipes
nx g p cart-total-count --project=cart-ui-pipes
Добавим логику пайпам:
Как видно из реализации, имеются следующие pipe’ы:
cart-product-price
— пайп, который возвращает цену одного товара с учетом его количества в корзине.cart-total-count
— пайп, который возвращает общее количество товаров в корзинеcart-total-price
— пайп, который возвращает полную стоимость всех товаров
Cart Card
Создадим карточку товара в корзине.
Я не удержался создать компонент cart-card :)
Создадим библиотеку:
nx g lib cart/ui/card
Добавим компонент:
nx g c cart-card --project=cart-ui-card
Так как компонент поддерживает 3 платформы, все данные были вынесены в соответствующие компоненты:
В основном большая часть компонентов выводит переданный контент, за редким исключением загружая дополнительные данные.
В результате получим:
В планшете:
В телефоне:
Cart List
Создадим компонент, который будет выводить список товаров.
Создадим библиотеку:
nx g lib cart/ui/list
Добавим компонент:
nx g c cart-list --project=cart-ui-list
Компонент с помощью CartFacade
получает список товаров:
export class CartListComponent implements OnInit {
cartProducts$!: Observable<CartProduct[]>;
constructor(private readonly cartFacade: CartFacade, @Inject(PATHS) public readonly paths: NavigationPaths) {}
ngOnInit(): void {
this.cartProducts$ = this.cartFacade.cartProducts$;
}
trackByFn(index: number, cartProduct: CartProduct): string {
return `${cartProduct.productId}_${cartProduct.size}`;
}
}
Где затем с помощью AsyncPipe
подписывается на значения и выводит список товаров используя ранее созданный компонент карточки товара — CartCard
:
<ng-container *ngIf="cartProducts$ | async; let cartProducts">
<ng-container *ngIf="cartProducts.length; else emptyCart">
<banshop-cart-card
automation-id="card"
[cartProduct]="cartProduct"
*ngFor="let cartProduct of cartProducts; trackBy: trackByFn"
></banshop-cart-card>
</ng-container>
<ng-template #emptyCart>
<div automation-id="empty" i18n="Cart list|Empty cart">
There are no products in your cart yet. Go to shopping - <a [routerLink]="paths.home | path">catalog</a>.
</div>
</ng-template>
</ng-container>
В итоге получим:
Cart Info
Реализуем компонент, который будет выводить список товаров и отображать их итоговую стоимость.
Сгенерируем библиотеку:
nx g lib cart/ui/info
Добавим компонент:
nx g c cart-info --project=cart-ui-info
Как и в случае со списком, получаем список товаров и подписываемся в компоненте.
Для вывода информации о товарах используем ранее созданные pipe’ы.
В результате получим компонент:
На странице будет смотреться следующим образом:
Cart Add
Создадим библиотеку:
nx g lib cart/ui/add
Добавим сервис:
nx g s cart-add --project=cart-ui-add
Так как при вызове метода сервиса будет показываться диалоговое окно, создадим компонент диалогового окна.
nx g m cart-add-dialog --project=cart-ui-add
nx g c cart-add-dialog --project=cart-ui-add
Реализуем сервис cart-add.service.ts
:
В данном случае, сервис имеет один метод, который открывает диалоговое окно, в которое передается выбранный товар.
Добавим логику компоненту диалогового окна:
Как видно в примере, в диалоговом окне уже выводится результат успешного добавления товара в корзину и пользователю предлагается либо продолжить покупки, либо перейти в корзину.
Cart Page
После того, как все компоненты созданы, можно перейти к созданию страницы корзины.
Добавим новую библиотеку, которая будет страницей корзины:
nx g lib cart/page
Добавим компонент:
nx g c cart-page --project=cart-page
Добавим модуль роутинга:
nx g m cart-page-routing --project=cart-page
Так как страница представляет собой всего-лишь вывод ранее созданных компонентов, то и шаблон страницы достаточно прост:
<h1 i18n="Cart page|Cart title">Cart</h1>
<banshop-row web>
<banshop-column web="9">
<banshop-cart-list automation-id="list"></banshop-cart-list>
</banshop-column>
<banshop-column web="3">
<banshop-cart-info automation-id="info"></banshop-cart-info>
</banshop-column>
</banshop-row>
В итоге, страница будет выглядеть:
В мобильной версии:
Ссылки
Предыдущая статья — Модуль товаров.
Следующая статья — Модуль оформления заказа.
Все исходники находятся на github, в репозитории:
Для того, чтобы посмотреть состояние проекта на момент написания статьи, нужно выбрать соответствующий тег — article.
Подписывайтесь на блог, чтобы не пропустить новые статьи про Angular, и веб-разработку. Medium | Telegram| VK |Tw| Ln