Build an Ecommerce App with Personalization using Angular and Cosmic JS
Tags: angular, cosmicjs, javascript, typescript
Published: 2019-05-02 16:27:55

---
title: Build an Ecommerce App with Personalization using Angular and Cosmic JS
published: true
description: 
tags: angular, cosmicjs, javascript, typescript
---
![Ecommerce App with Personalization using Angular and Cosmic JS](https://cosmic-s3.imgix.net/e1a1fb30-6c30-11e9-8d98-25edff7d0d98-Cosmic-Customization.png?w=1000)

_\* This article will assume some basic knowledge of Angular so it can focus on the specific task at hand. Feel free to ask me about any specifics on the implementation you may find unclear_

  
### **TL; DR**

Take a look at [the repository](https://github.com/cosmicjs/angular-ecommerce-personalization) and [install the app](https://cosmicjs.com/apps/ecommerce-personalization), or [view a demo](https://cosmicjs.com/apps/ecommerce-personalization/demo)



## What are we going to build?

This site will be a very simplistic representation of an ecommerce website and its purpose is to show how we can offer a customized experience for everyone. Our data will be stored and served by Cosmic JS and we will use Angular for our Front-End.

![Ecommerce App with Personalization using Angular and Cosmic JS](https://cosmic-s3.imgix.net/bef19600-6ad6-11e9-85d0-9fa707d6dd21-Screenshot-2019-04-30-at-01.24.05.png?w=1000)

### How will it work?

Our storefront will be just a product listing page, showing all products stored in Cosmic JS, randomly sorted. The products will belong to a number of categories, and each product will show 3 buttons to mock the following behaviors: 
* view -> value: 1
* add to cart -> value: 2
* buy -> value: 3

These will represent the different degrees of interest.  

The user will interact with the site and its interests will be stored in its profile, increasing the corresponding value of each product's categories.

A full interaction would be something like: the user clicks on "buy" a "swimwear" product, increasing its profile score for products belonging to "swimwear". The page then will sort the page showing more prominently the products belonging to "swimwear".


## Preparing our bucket

The first thing we'll do is prepare our Cosmic JS bucket. We have to create an empty bucket and three object types:
* Categories
* Products
* Users

Each `user` will store a `sessionID` and a JSON object for `interests`.
Each `product` will have a `price`, a collection of `categories` and an `image`.
Categories won't need any metadata, we will just use its `title`.

If you are as lazy as I am, you can always **replicate the demo bucket** by [installing the app](https://cosmicjs.com/apps/ecommerce-personalization).

## Preparing the site

The first thing we need to do is create a new Angular site (I always recommend using the CLI for that). Now, let's create a HTTP interceptor that will handle the authentication against the Cosmic JS API, so we don't need to add this in every call. It will append the `read_key` parameter for the GET requests and the `write_key` for everything else.

```javascript
@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);
  }
}
```

The product listing will be our only page, so after creating the component, our routes should look like this:
```javascript
const routes: Routes = [{ path: '', component: ProductListingComponent }];
```

## The product listing page

We need to show the products, so let's define our model:
```javascript
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)));
    }
  }
}
```
_\*We'll talk about the Category there later._

Now let's begin populating our service with methods. First of all, we need a `getProducts()` method:
```javascript
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$;
  }
```
_\*We are caching the response, our products won't change that often and we are going to call this method A LOT..._

The component itself will be quite simple. For now, it will only have the call two calls to our service:
```javascript
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));
  }
}
```
And this is the 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>
```

Have you noticed that `app-actions` component? It will take care of the actions the user will perform over each product. But let's talk about the `user` before that...

## Everything about the user

Our user model will be the most "complex" of all. As we will be creating users from the site and retrieving them from Cosmic JS, we need a more flexible constructor. We also added methods to build a payload object for posting to Cosmic JS and another one for managing the interests.

```javascript
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;
    }
  }
}
```

We won't build a proper authentication journey for our example. Each user will have a sessionID on the browser's local storage, and we will use that to create and identify users. We'll keep the user instance on session storage for convenience. In order to keep things tidy, let's create a `user` service:
```javascript
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;
    }
  }
}
```

The `init()` method will be used on our `app.component.ts`:
```javascript
constructor(private userService: UserService) {}

  ngOnInit() {
    this.userService.init();
  }
```

We will also need a way of setting and getting the user, let's extend our `cosmic.service` with the following methods:
```javascript
  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>())
    );
  }
```

Also note that we created an observable `user$` that emits every time we set (or update) the user in session, you already saw the subscription on the product listing. More on this at the end of the article.

Back to the `actions.component`. It is a set of three buttons, as follows:
```
<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>
```
And the methods look like this:

```javascript
  @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();
  }
```

When updating the interests, we also refresh the user in session and we also update the user in Cosmic JS, we never know when or how the user will leave the site! Let's go back to our `cosmic.service` and add this method:
```javascript
updateUser(user: User) {
    return this.http.put<User>(this.editObjectPath, JSON.stringify(user.putBody())).pipe(
      map(_ => {
        return new User(_['object']);
      }),
      catchError(this.handleError<User>())
    );
  }
```

## Wrapping everything up

We have now a way to show products, create users and their interests, update them and store them. That's it... almost. How does the product listing page change based on all this? There is one last thing we haven't go through yet: the customSort pipe on `product-listing.component`. Here is how it looks like:

```javascript
@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;
  }
}
```

This pipe will take the product list and order it by weight, based on the user's interests provided via arguments...

... And that's the magic: the product listing page is subscribed to the changes in the session user, so every time the user performs an action, the user changes and the sorting re-executes with the changes.

That's all, I hope this would have demonstrated how to offer a customized experience for your users with the help of Angular and Cosmic JS.