Сайт визитка на Angular. Модуль корзины.

Aleksandr Serenko
F.A.F.N.U.R
Published in
6 min readMar 27, 2022
Сайт визитка на 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

--

--

Aleksandr Serenko
F.A.F.N.U.R

Senior Front-end Developer, Angular evangelist, Nx apologist, NodeJS warlock