In my last guide, we talked about API Driven Development.
Let's get more pragmatic this time and build an e-commerce SPA using Google Firebase as the backend that issues the APIs, Angular 4 for the front-end Javascript framework and Material design to help us out with the styling and scaffolding.
Just before we get started, please note that in real life application, I will not use Firebase for an e-commerce application. It has its advantages though, and definitely can be used for most applications. But for a full featured e-commerce application, you might have to build your back-end using any programming language of your choice. The most important thing is that you have endpoints that your front-end can communicate with.
Also, if you do not have NodeJs installed on your computer, navigate to their website, download and install the source code for your OS. The easiest way to install it might be to install it using the package manager..

Lets get started by installing Angular CLI. Open your terminal and run

npm install -g @angular/cli

This will install angular CLI globally on your computer and we will use it to speed up our development process.
Navigate to your dev. directory and run

ng new apfem

Replace apfem with the name you want to call your project. Navigate to your project folder cd ./Documents/dev./apfem, and run

npm install --save @angular/material @angular/cdk
npm install --save @angular/animations
npm install --save hammerjs

It will install Angular Material which we will use for styling. Feel free to customize it to your taste. Lets start our application by running

ng serve

This will spin up a server on port 4200 localhost:4200
One of the most important aspect of our development is the ability to lazy load components (initialization of an object or component only when it is needed). Thanks to Angular CLI, we can use it to generate our module. Open another terminal, navigate to your project folder and run

ng g m items

this will generate a folder called items and add a file in it called items.module.ts.
Next, lets create an item service that we will use in handling our http protocols.

ng g s items/item

Paste the code below inside the item.service.ts

...
import { Injectable } from '@angular/core';
import { Headers, Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Rx';

import { Item } from './item';

@Injectable()
export class ItemService {
  private header = new Headers({'Content-Type': 'application/json'});
  private firbaseApi = 'https://apfem-78c34.firebaseio.com/';

  /**
   * Item sample data
   */
  private items: Item[] = [
    new Item('angular-t-shirt', 'Angular T-Shirt', new Category('t-shirt', 'T-Shirts'), 120, 135, 'In Stock', 'Lorem ipsum dolor sit amet, no agam populo apeirian pri, ea eirmod scaevola voluptatibus per. No est maluisset sadipscing, duo soluta dignissim dissentiet ei, malis possim posidonium ad has. Dico error utamur an est, ex tantas expetendis sit. Convenire disputando repudiandae nam no, laudem malorum quaeque sit id.',
    [
      new Image('http://devstickers.com/assets/img/pro/tee/8ki7_mhoodiez.png'),
      new Image('http://devstickers.com/assets/img/pro/tee/lrj4_mens.png')
    ]),
    new Item('gulp-sass-cup', 'Gulp & Sass Cup', new Category('cup', 'Cups'), 90, 105, 'Pre-Order', 'Lorem ipsum dolor sit amet, no agam populo apeirian pri, ea eirmod scaevola voluptatibus per. No est maluisset sadipscing, duo soluta dignissim dissentiet ei, malis possim posidonium ad has. Dico error utamur an est, ex tantas expetendis sit. Convenire disputando repudiandae nam no, laudem malorum quaeque sit id.',
    [
      new Image('http://www.joel-chrabie.com/res/js/gulpsass.png'),
      new Image('http://vesparny.github.io/angular-kickstart/assets/images/gulp-logo.png')
    ]),
    new Item('google-cup', 'Google Cup', new Category('cup', 'Cups'), 96, 117, 'In Stock', 'Lorem ipsum dolor sit amet, no agam populo apeirian pri, ea eirmod scaevola voluptatibus per. No est maluisset sadipscing, duo soluta dignissim dissentiet ei, malis possim posidonium ad has. Dico error utamur an est, ex tantas expetendis sit. Convenire disputando repudiandae nam no, laudem malorum quaeque sit id.',
    [
      new Image('https://images-na.ssl-images-amazon.com/images/I/71aEJmSmIYL._SX355_.jpg'),
      new Image('http://www.mypeyronies.com/image-files/hourglass-penis-indentations.jpg')
    ])
  ];

  constructor( private http: Http ) { }
  /**
   * Get items from the firebase API
   */
  getItems() {
    return this.http.get(this.firbaseApi + 'items.json')
                    .map((response: Response) => response.json() as Item[]);
  }

  /**
   * Get an item from the items array by id
   * @param id
   */
  getItem(id: number) {
    return this.items[id];
  }

  /**
   * Used to send/store items to the backend
   */
  storeItem() {
    const body = JSON.stringify(this.items);
    return this.http.put(this.firbaseApi + 'items.json', body, {headers: this.header});
  }
...

Notice that we imported an Item model import { Item } from './item';? Create the model item.ts and paste the following:

export class Item {
    constructor(
        public slug: string,
        public name: string,
        public category: Category,
        public price: number,
        public old_price: number,
        public availability: string,
        public description: string,
        public photos: Image[]
    ) {}
}

export class Image {
    constructor(public path: string) {}
}

export class Category {
    constructor(public slug: string, public title: string) {}
}

Strong typing is one good reason why I like Angular - Thanks to Typescript. If you've ever worked as a back-end developer, you will understand how important type definitions are.
Anyways, lets create our first component in the items module by running

ng g c items/item-list --flat

By default the CLI will generate a component in a new folder and call it the name of your component. We did override that default behavior by appending --flat to the command. The CLI will also update our items.module.ts file and import our items component to its declaration.
Open the item-list.component.ts file and add this inside

...
import { ItemService, Item } from './index';
...
export class ItemListComponent implements OnInit {

  items: Item[] = [];

  constructor(
    private itemService: ItemService
  ) { }

  /**
   * Get the items from the service
   */
  getItems() {
    this.itemService.getItems().subscribe(
      data => {
        this.items = data
      }
    );
  }

  ngOnInit() {
    this.getItems();
  }
}

Open the item-list.component.html file and paste this inside

<div class="content">
  <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
    <div>List of items</div>
    <div class="row" *ngIf="items">
      <apfem-item 
        *ngFor="let item of items; let i = index" 
        [item]="item" [selectedItem]="i"
        class="col-xs-12 col-sm-6 col-md-4 col-lg-3"
      >
      </apfem-item>
    </div>
  </div>
</div>

Noticed that we are injecting another component with its selector called<apfem-item></apfem-item and looping through the component with our items array? Well I'm assuming that somewhere in future I might have to reuse the same data structure, So I want us to keep our code DRY. Create that component by running

ng g c items/item ---flat

Add this to the item.component.ts file

import { Component, OnInit, Input } from '@angular/core';

import { Item } from './index';

...
export class ItemComponent implements OnInit {

  @Input() item: Item;
  @Input() selectedItem: number;

  ngOnInit() {}

}

Here we are receiving the item and selectedItem property that we sent from the `item-list.component.html
Open the html file of item component and paste this:

<a [routerLink]="['/items', selectedItem]">
  <md-card class="center-xs item-card" *ngIf="item">
    <div class="item-img" [ngStyle]="{'background': 'white url(' + item.photos[0].path +')',
      'background-repeat': 'no-repeat','background-size': 'cover','width': 'auto','min-height': '250px',
      'background-position': 'center center','color': '#FFF' }"></div>
    <md-card-content>
      <div>
        <md-card-header class="center-xs">
          <md-card-title>
            <h3>{{ item.name }}</h3>
            <span>{{ item.price | currency:'USD':true }}</span>
            <small class="old-price">{{item.old_price | currency:USD:true}}</small>
          </md-card-title>
        </md-card-header>
      </div>
    </md-card-content>
    <button class="end-xs" md-raised-button color="primary">Add To Cart <md-icon>add_shopping_cart</md-icon></button>
  </md-card>
</a>

Noticed that we've been using Material design component in items module html files? Well at this point I know our terminal is probably filled with United and Liverpool jersey, lets try getting it back to Celtic. Add the code below to the items.module.ts file

...
import { MaterialModule } from '@angular/material';
import { ReactiveFormsModule } from '@angular/forms';
...
imports: [
...
MaterialModule
...

We should be able to view a single item on a separate page and then add it to our cart. Right? Lets create a separate component for that:

ng g c items/item-details --flat

Paste this bunch of code inside it. I will leave you to import the necessary modules. At this point, am sure you've been following through.

  delay = 5000;
  loop = false;
  item: Item;
  cartForm: FormGroup;
  itemIndex: any;
  // You can write a simple loop for this
  numbers = [
    {value: 1, display: 'One'},
    {value: 2, display: 'Two'},
    {value: 3, display: 'Three'},
    {value: 4, display: 'Four'},
    {value: 5, display: 'Five'}
  ];

  constructor(
    private route: ActivatedRoute,
    private itemService: ItemService,
    private snackBar: MdSnackBar,
    private cartService: CartService
  ) {
    this.cartForm = new FormGroup({
      'item': new FormControl('', Validators.required),
      'quantity': new FormControl('', Validators.required)
    });
  }

  ngOnInit() {
    this.getItem();
  }

  /**
   * Get an item from the items array using the slug parameter
   */
  getItem() {
    this.route.params.subscribe(
      (params: any) => {
        this.itemIndex = params['slug'];
        this.item = this.itemService.getItem(this.itemIndex);
      }
    );
  }

  /**
   * Add item to cart(users browser - localStorage)
   */
  onAddToCart() {
    if (localStorage.getItem('cart-' + this.item.slug)) {
      this.snackBar.open('Already in your cart!', 'Ok', {
        duration: 5000,
      });
    } else {
      this.cartService.addItemToCart(this.cartForm.value);
      localStorage.setItem('cart-' + this.item.slug, JSON.stringify(this.cartForm.value));
      this.snackBar.open('Successfully added to cart!', 'Dismiss', {
        duration: 5000,
      });
    }
  }

Whats going on here?
Firstly, we used the Angular ActivatedRoute module to get the active route that we just navigated to in our getItem() {} method, we assigned it to the itemIndex variable that we initialized, and then fetched it using the getItem() {} method we created in our item.service.ts. We then initialized it in our ngOnInit() {} Lifecycle hook.
Secondly, we added a method that will add the item in the users cart(browser localStorage), show the user a little message when they try adding the item to there cart. Great! Lets move on and add the html for the item details component.

<div><a routerLink="/items">Back to items</a></div>
<div class="row" *ngIf="item">
    <div class="col-xs-12 col-sm-7 col-md-7">
        <apfem-carousel [interval]="delay" [noWrap]="loop">
            <apfem-slide *ngFor="let photo of item.photos" [active]="photo.active" class="img">
                <md-card>
                    <div md-card-image [ngStyle]="{ 'background': 'white url(' + photo.path + ')',
                    'background-repeat': 'no-repeat','background-size': 'cover','width': 'auto','height': '60vh',
                    'background-position': 'center center','color': '#FFF' }">
                    </div>
                </md-card>
            </apfem-slide>
        </apfem-carousel>
    </div>
    <div class="col-xs-12 col-sm-5 col-md-5">
        <h3>{{item.name}}</h3>
        <h4>{{item.price | currency:USD:true}}</h4>
        <p><small class="old-price">{{item.old_price | currency:USD:true}}</small></p>
        <p><small>Availability<br><b>{{item.availability}}</b></small></p><br>
        <form (ngSubmit)="onAddToCart()" [formGroup]="cartForm">
            <input type="hidden" formControlName="item" [(ngModel)]="item">
            <md-select placeholder="Quantity" formControlName="quantity">
                <md-option *ngFor="let qty of numbers" [value]="qty.value">
                    {{ qty.display }}
                </md-option>
            </md-select>
            <button md-button md-raised-button color="accent" [disabled]="!cartForm.valid">
                <md-icon>add_shopping_cart</md-icon> ADD TO CART
            </button>
        </form>
    </div>
</div>
<div class="row">
    <div class="col-xs-12">
        <small><b>Description:</b></small>
        <p innerHTML="{{ item.description }}"></p>
    </div>
</div>

How about the <apfem-carousel></apfem-carousel> carousel component that we just injected? Well, I figured out that I had a carousel component that I added to the homepage. I'm just trying to be a super programmer :) Anyways, this guide is getting too long so lets save some time. All the files are in the Github repo. of this guide so lets move on an do two last things.
Lets update our application route so that our navigation's will work.
Open app.touting.ts file and add this:

export const routes: Routes = [
...
   {
        path: 'items',
        component: DefaultLayoutComponent,
        children: [
            {
                path: '',
                loadChildren: 'app/items/items.module#ItemsModule'
            }
        ]
    },
];

Create a file named item-routing.module.ts in the items folder and paste this:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { 
    ItemDetailsComponent,
    ItemListComponent
} from './index';

const routes: Routes = [
    {
        path: '',
        component: ItemListComponent
    },
    {
        path: ':slug',
        component: ItemDetailsComponent
    }
];

@NgModule({
    imports: [RouterModule.forChild(routes)],
    exports: [RouterModule]
})
export class ItemRoutingModule {}

That's how we handle the lazy loading that we talked about earlier.
Preview your app on the browser.


All the files with additional features are in the repository

 

More Articles...


DevOps, The Perfect Guide For Every Engineer. Part 1

Then came DevOps which is a combination of cultural philosophy, practices, tools & IT teams aimed at increasing the speed, efficiency & security of software development & delivery. Simply put, it's a combination of software development (dev) & operations (ops).