Создайте приложение электронной коммерции с персонализацией, используя Angular и Cosmic JS.

* В этой статье предполагается наличие некоторых базовых знаний об Angular, чтобы можно было сосредоточиться на конкретной задаче. Не стесняйтесь спрашивать меня о любых особенностях реализации, которые могут показаться вам неясными.

тл; ДР

Взгляни на хранилище а также установить приложениеили же посмотреть демо

Что будем строить?

Этот сайт будет очень упрощенным представлением веб-сайта электронной коммерции, и его цель — показать, как мы можем предложить индивидуальный подход для всех. Наши данные будут храниться и обслуживаться Cosmic JS, и мы будем использовать Angular для нашего внешнего интерфейса.

bef19600-6ad6-11e9-85d0-9fa707d6dd21-Скриншот-2019-04-30-at-01.24.05.png

Как это будет работать?

Наш магазин будет просто страницей со списком продуктов, на которой будут показаны все продукты, хранящиеся в Cosmic JS, отсортированные случайным образом. Продукты будут принадлежать к нескольким категориям, и каждый продукт будет иметь 3 кнопки для имитации следующих действий:

  • вид -> значение: 1
  • добавить в корзину -> значение: 2
  • купить -> значение: 3

Они будут представлять различные степени заинтересованности.

Пользователь будет взаимодействовать с сайтом, и его интересы будут храниться в его профиле, увеличивая соответствующую ценность каждой категории продукта.

Полное взаимодействие будет выглядеть примерно так: пользователь нажимает «купить» продукт «купальники», увеличивая свой рейтинг профиля для продуктов, принадлежащих «купальникам». Затем страница будет сортировать страницу, показывая более заметные продукты, принадлежащие к «купальникам».

Подготавливаем наше ведерко

Первое, что мы сделаем, это подготовим нашу ведро Cosmic JS. Нам нужно создать пустое ведро и три типа объектов:

  • Категории
  • Товары
  • Пользователи

Каждый user будет хранить sessionID и объект JSON для interests.
Каждый product будет иметь priceколлекция categories и image.
Категории не будут нуждаться в каких-либо метаданных, мы просто будем использовать их title.

Если вы так же ленивы, как и я, вы всегда можете воспроизвести демо-ковш по установка приложения.

Подготовка сайта

Первое, что нам нужно сделать, это создать новый сайт Angular (я всегда рекомендую использовать для этого интерфейс командной строки). Теперь давайте создадим перехватчик HTTP, который будет обрабатывать аутентификацию с помощью Cosmic JS API, поэтому нам не нужно добавлять это при каждом вызове. Он добавит read_key параметр для запросов GET и write_key для всего остального.

@Injectable({ providedIn: 'root' })
export class CosmicInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { if (req.url.match(/api.cosmicjs/)) { let params = new HttpParams({ fromString: req.params.toString() }); if (req.method === 'GET') { params = params.append('read_key', environment.read_key); req = req.clone({ params: params }); } else { let payload = JSON.parse(req.body); payload.write_key = environment.write_key; req = req.clone({ body: payload }); } } return next.handle(req); }
}

Список продуктов будет нашей единственной страницей, поэтому после создания компонента наши маршруты должны выглядеть так:

const routes: Routes = [{ path: '', component: ProductListingComponent }];

Страница со списком продуктов

Нам нужно показать продукты, поэтому давайте определим нашу модель:

import { Category } from './category'; export class Product { _id: string; slug: string; title: string; price: string; categories: Category[]; image: string; constructor(obj) { this._id = obj._id; this.slug = obj.slug; this.title = obj.title; this.price = obj.metadata.price; this.image = obj.metadata.image.url; this.categories = []; if (obj.metadata && obj.metadata.categories) { obj.metadata.categories.map(category => this.categories.push(new Category(category))); } }
}

* Мы поговорим о категории позже.

Теперь давайте начнем наполнять наш сервис методами. Прежде всего, нам нужен getProducts() метод:

private products$: Observable<Product[]>; getProducts(): Observable<Product[]> { if (!this.products$) { this.products$ = this.http.get<Product[]>(this.productsUrl).pipe( tap(_ => console.log('fetched products')), map(_ => { return _['objects'].map(element => new Product(element)); }), shareReplay(1), catchError(this.handleError('getProducts', [])) ); } return this.products$; }

* Мы кэшируем ответ, наши продукты не будут меняться так часто, и мы будем вызывать этот метод МНОГО…

Сам компонент будет достаточно простым. На данный момент у него будет только два звонка в нашу службу:

import { Component, OnInit } from '@angular/core';
import { CosmicService } from 'src/app/core/_services/cosmic.service';
import { Product } from '@models/product';
import { UserService } from 'src/app/core/_services/user.service';
import { User } from '@models/user'; @Component({ selector: 'app-product-listing', templateUrl: './product-listing.component.html', styleUrls: ['./product-listing.component.scss']
})
export class ProductListingComponent implements OnInit { public productList: Product[]; public user: User; constructor(private cosmicService: CosmicService, private userService: UserService) {} ngOnInit() { this.userService.user$.subscribe(user => { this.user = user; }); this.cosmicService.getProducts().subscribe(products => (this.productList = products)); }
}

А это HTML:

<div class="columns" *ngIf="productList && user">
  <ng-container *ngFor="let product of (productList | customSort:user.interests)">
          <div class="product-tile column is-one-third">
            <img src="{{ product.image }}" class="image"/>
            <div class="level is-size-4 is-uppercase">
                <span class="level-item">{{product.title}}</span>
                <span class="level-item has-text-weight-bold">${{product.price}}</span>
            </div>
            <app-actions [product]="product"></app-actions>
          </div>
  </ng-container>
</div>

Вы заметили, что app-actions составная часть? Он позаботится о действиях, которые пользователь будет выполнять над каждым продуктом. Но давайте поговорим о user до этого…

Все о пользователе

Наша пользовательская модель будет самой «сложной» из всех. Поскольку мы будем создавать пользователей с сайта и получать их из Cosmic JS, нам нужен более гибкий конструктор. Мы также добавили методы для создания объекта полезной нагрузки для публикации в Cosmic JS и еще один для управления интересами.

import { Category } from './category'; export class User { _id: string; slug: string; interests: JSON; constructor(obj?) { this._id = obj ? obj._id : ''; this.slug = obj ? obj.slug : ''; this.interests = obj ? JSON.parse(obj.metadata.interests) : {}; } postBody() { return { title: this.slug, type_slug: 'users', metafields: [{ key: 'interests', value: JSON.stringify(this.interests) }] }; } putBody() { return { title: this.slug, slug: this.slug, metafields: [{ key: 'interests', value: JSON.stringify(this.interests) }] }; } increaseInterest(category: Category, weight: number) { if (!this.interests[category.title]) { this.interests[category.title] = weight; } else { this.interests[category.title] += weight; } }
}

Мы не будем строить надлежащий путь аутентификации для нашего примера. У каждого пользователя будет идентификатор сеанса в локальном хранилище браузера, и мы будем использовать его для создания и идентификации пользователей. Мы сохраним пользовательский экземпляр в хранилище сеансов для удобства. Чтобы все было в порядке, давайте создадим user оказание услуг:

import { Injectable } from '@angular/core';
import { User } from '@models/user';
import { CosmicService } from './cosmic.service';
import { BehaviorSubject } from 'rxjs'; @Injectable({ providedIn: 'root'
})
export class UserService { private userSource = new BehaviorSubject<User>(new User()); public user$ = this.userSource.asObservable(); constructor(private cosmicService: CosmicService) {} init() { let sessionID = localStorage.getItem('sessionID'); if (!sessionID) { const user = new User(); sessionID = Math.random() .toString(36) .substr(2, 9); localStorage.setItem('sessionID', sessionID); user.slug = sessionID; this.cosmicService.setUser(user).subscribe(user => { this.setSessionUser(user); }); } else if (!sessionStorage.getItem('user')) { this.cosmicService.getUser(sessionID).subscribe(user => this.setSessionUser(user)); } } setSessionUser(user: User) { sessionStorage.setItem('user', JSON.stringify(user)); this.userSource.next(user); } getSessionUser(): User { const user = sessionStorage.getItem('user'); if (user) { return Object.assign(new User(), JSON.parse(user)); } else { return null; } }
}

init() метод будет использоваться на нашем app.component.ts:

constructor(private userService: UserService) {} ngOnInit() { this.userService.init(); }

Нам также понадобится способ настройки и получения пользователя, давайте расширим наш cosmic.service следующими методами:

getUser(slug: string): Observable<User> { const url = `${this.singleObjectUrl}/${slug}`; return this.http.get<User>(url).pipe( tap(_ => console.log(`fetched user: ${slug}`)), map(_ => { return new User(_['object']); }), catchError(this.handleError<User>(`getUser: ${slug}`)) ); } setUser(user: User) { return this.http.post<User>(this.addObjectPath, JSON.stringify(user.postBody())).pipe( map(_ => { return new User(_['object']); }), catchError(this.handleError<User>()) ); }

Также обратите внимание, что мы создали наблюдаемую user$ который испускается каждый раз, когда мы устанавливаем (или обновляем) пользователя в сеансе, вы уже видели подписку в списке продуктов. Подробнее об этом в конце статьи.

Назад к actions.component. Это набор из трех кнопок, а именно:

<div class="buttons has-addons is-centered">
  <a class="button is-primary" (click)="viewProduct()">
    <span class="icon is-small">
      <i class="fa fa-eye"></i>
    </span>
    <span>View</span>
  </a>
  <a class="button is-warning" (click)="addProductToCart()">
    <span class="icon is-small">
      <i class="fa fa-shopping-cart"></i>
    </span>
    <span>Add to cart</span>
  </a>
  <a class="button is-success" (click)="buyProduct()">
    <span class="icon is-small">
      <i class="fa fa-dollar"></i>
    </span>
    <span>Buy!</span>
  </a>
</div>

И методы выглядят так:

@Input() product: Product; viewProduct() { this.increaseInterest(1); } addProductToCart() { this.increaseInterest(2); } buyProduct() { this.increaseInterest(3); } increaseInterest(weight: number) { const user: User = this.userService.getSessionUser(); this.product.categories.forEach((category: Category) => { user.increaseInterest(category, weight); }, this); this.userService.setSessionUser(user); this.cosmicService.updateUser(user).subscribe(); }

При обновлении интересов мы также обновляем пользователя в сеансе, а также обновляем пользователя в Cosmic JS, мы никогда не знаем, когда и как пользователь покинет сайт! Вернемся к нашему cosmic.service и добавьте этот метод:

updateUser(user: User) { return this.http.put<User>(this.editObjectPath, JSON.stringify(user.putBody())).pipe( map(_ => { return new User(_['object']); }), catchError(this.handleError<User>()) ); }

Завершение всего

Теперь у нас есть способ показывать продукты, создавать пользователей и их интересы, обновлять их и сохранять. Вот так… почти. Как меняется страница со списком продуктов на основе всего этого? Есть еще одна вещь, которую мы еще не рассмотрели: канал customSort на product-listing.component. Вот как это выглядит:

@Pipe({ name: 'customSort'
})
export class CustomSortPipe implements PipeTransform { transform(value: Product[], interests: JSON): Product[] { value.sort((a: Product, b: Product) => { const aWeight = this.getWeight(a.categories, interests); const bWeight = this.getWeight(b.categories, interests); if (aWeight < bWeight) { return 1; } else if (aWeight > bWeight) { return -1; } else { return 0; } }); return value; } getWeight(categories: Category[], interests: JSON) { let weight = 0; categories.forEach(category => { weight += interests[category.title] || 0; }); return weight; }
}

Эта труба возьмет список продуктов и упорядочит его по весу, основываясь на интересах пользователя, предоставленных с помощью аргументов…

… И в этом волшебство: страница со списком продуктов подписывается на изменения пользователя сеанса, поэтому каждый раз, когда пользователь выполняет действие, пользователь изменяется, и сортировка выполняется повторно с изменениями.

Вот и все, я надеюсь, что это продемонстрировало, как предложить пользователям индивидуальный подход с помощью Angular и Cosmic JS.

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *