facebook
Bob at Genuitec
Virtual evangelist at large. The face of Genuitec often appearing in graphics. Pen-name for our more shy writers of content.
Posted on Jun 21st 2017

In this Angular 4 tutorial we are going to create a dashboard for an e-commerce store which will highlight key metrics and show top selling products. In the course of this tutorial, we will be using Angular IDE.

Let’s get started by firing up our Angular IDE and create a new Angular project named `StoreReporter`.

storereporter-angular-project

Next, we create a single reusable data service StoreService for our data needs, by clicking File > New > Service.

storereporter-store-service

Store summary cards

We are going to add three cards to our dashboar; the metrics on the cards will be New Customers, Active Users and Sales. We will refer to the three cards as summary cards, and they will look like the image below.

storereporter-summary-cards

Let us add some stub methods to StoreService.

import { Injectable } from '@angular/core';

@Injectable()
export class StoreService {
  constructor() { }
  getNewCustomersCount() { } // stub
  getActiveUsersCount() { } // stub
  getSalesSum() { } // stub
}

Each summary card has a name and value, for example the first card has a name `New customers` and content `147`. Let us create a class StoreSummary in a new TypeScript source file `src/app/store-summary.ts` with these two properties.

export class StoreSummary {
  name: string;
  content: string;
}

In this article we will not be getting our data from a remote api, rather we will be using mock data. So we will create a new file `mock-store-summary.ts` with the following content:

import { StoreSummary } from './store-summary';

export let NEW_CUSTOMERS_COUNT: StoreSummary = {
  name: 'New customers',
  content: '147',
};

Let us use this mock data in our store service. first we add the following imports to the top of `src/app/store.service.ts`.

import { StoreSummary } from './store-summary';
import { NEW_CUSTOMERS_COUNT } from './mock-store-summary';

Then we update the `getNewCustomersCount` method to become

getNewCustomersCount(): Promise<StoreSummary> {
    return Promise.resolve(NEW_CUSTOMERS_COUNT);
  }

Because data services are invariably asynchronous, we are returning a promise in the `getNewCustomersCount` method. Next we will create a summary card component right clicking on the app folder in the project explorer, select new, then click component. We set the component name as `summary-card`, Angular ide does the following
* Creates a directory named `summary-card`
* Creates four files `summary-card.component.css`, `summary-card.component.html`, `summary-card.component.spec.ts` and `summary-card.component.ts`
* Updates `app.module.ts` by importing `SummaryCardComponent` and adding it to the declarations array.

The `summary-card-component.html` contains a `p` element with the content ‘summary-card works!’. Let us render three instances of the summary card in our app, to do this we update `app.component.html` by adding the following lines of code:

<app-summary-card></app-summary-card>
<app-summary-card></app-summary-card>
<app-summary-card></app-summary-card>

Our dashboard app now looks like this

storeporter-work-confirmation
Let us prepare `app/src/summary-card.component.html` to render data from a `storeSummary object`:

<p>
  {{storeSummary.name}}
  {{storeSummary.content}}
</p>

Next we modify `src/app/summary-card/summary-card.component.ts` to add a `StoreSummary property`. The code below shows new additions:

import { StoreSummary } from '../store-summary'; // add below top imports
...
 storeSummary: StoreSummary; // add above constructor() { }

Right now we have an error `Cannot read property name of undefined` because Angular is trying to access the name and content property of a `storeSummary` object which is undefined. We will fix this by adding an if condition to the p element in `app/src/summary-card/summary-card.component.html`.

<p *ngIf="storeSummary"> // modify the opening p tag

Later in this article the parent `AppComponent` will tell the child `SummaryCardComponent` which `storeSummary` to display by binding the an object to `storeSummary` of the `SummaryCardComponent`. The binding will look like this:

 <app-summary-card [storeSummary]="newCustomersCount"></app-summary-card>

Putting square brackets around the storeSummary property, to the left of the equal sign (=), makes it the target of a property binding expression. We must declare a target binding property to be an input property. Otherwise, Angular rejects the binding and throws an error.

First, we amend the @angular/core import statement to include the Input symbol.

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

Then declare that storeSummary is an input property by preceding it with the @Input decorator that we just imported.

@Input() storeSummary: StoreSummary;

Next we update `AppComponent` so we have:

import { Component, OnInit } from '@angular/core';
import { StoreService } from './store.service';
import { StoreSummary } from './store-summary';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'Store Reporter';
  newCustomersCount: StoreSummary;
  constructor (
    private storeService: StoreService
  ) {};

  getNewCustomersCount(): void {
      this.storeService.getNewCustomersCount().then(newCustomersCount => this.newCustomersCount = newCustomersCount );
  }

  ngOnInit(): void {
    this.getNewCustomersCount();
  }
}

We just imported `StoreService` and `StoreSummary`, and updated the `@angular/core import` to include `OnInit`. We also added a property `newCustomersCount` which is an instance of `StoreSummary`. Next, within the constructor, we added an instance of `StoreService` through dependency injection. Underneath the constructor() { }, we added the ngOnInit() lifecycle hook, which runs when the component loads. Finally, we added the `getNewCustomersCount` method and executed it in `ngOnInit`.

Now we have an error in our console `Error: No provider for StoreService!`; in order to fix this we need to add `StoreService` to the providers property of the NgModule decorator in `src/app/app.module.ts`.

import { StoreService } from './store.service'; // Add to imports at top of file
...
 providers: [StoreService],
...

Now our dashboard looks like this

storereporter-new-customers
Let us set up the remaining summary cards. First, we add two more mock objects to `app/src/mock-store-summary` with the following code snippet:

export let ACTIVE_USERS_COUNT: StoreSummary = {
  name: 'Active Users',
  content: '384',
};

export let SALES_SUM: StoreSummary = {
  name: 'Sales',
  content: '$1047',
};

Then we update the imports from `./mock-store-summary` in `app/src/store.service.ts`, and update the stub methods `getActiveUsersCount` and `getSalesSum`; the code below shows only the changes:

import { NEW_CUSTOMERS_COUNT,
         ACTIVE_USERS_COUNT,
         SALES_SUM
       } from './mock-store-summary';

...
  getActiveUsersCount(): Promise<StoreSummary> {
   return Promise.resolve(ACTIVE_USERS_COUNT);
  }
  getSalesSum(): Promise<StoreSummary> {
   return Promise.resolve(SALES_SUM);
  }
...

Next are the changes to `src/app/app.component.ts`:

import { Component, OnInit } from '@angular/core';
import { StoreService } from './store.service';
import { StoreSummary } from './store-summary';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  title = 'Store Reporter';

  newCustomersCount: StoreSummary;
  activeUsersCount: StoreSummary;
  salesSum; StoreSummary;

  constructor (private storeService: StoreService) {}

  getNewCustomersCount(): void {
      this.storeService.getNewCustomersCount().then(newCustomersCount => this.newCustomersCount = newCustomersCount );
  }

  getActiveUsersCount(): void {
    this.storeService.getActiveUsersCount().then(activeUsersCount => this.activeUsersCount = activeUsersCount);
  }

  getSalesSum(): void {
    this.storeService.getSalesSum().then(salesSum => this.salesSum = salesSum);
  }

  ngOnInit(): void {
    this.getNewCustomersCount();
    this.getActiveUsersCount();
    this.getSalesSum();
  }
}

Update `src/app/app.component.html`:

< h1>
  {{title}}
</h1>
<app-summary-card [storeSummary]="newCustomersCount"></app-summary-card>
<app-summary-card [storeSummary]="activeUsersCount"></app-summary-card>
<app-summary-card [storeSummary]="salesSum"></app-summary-card>

Now our dashboard looks like this:

storereporter-the-three-components

Let’s add some style to our dashboard, first we include Bootstrap 4 in `src/index.html`; the code below only shows the changes:

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">

Next we update `/src/app/summary-card/summary-card.component.html`:

< div class="card p-3">
    <p class="mb-0">{{storeSummary.name}}</p>
    <h4>{{storeSummary.content}}</h4>
</div>

and `/src/app/app.component.html`:

< div class="container pt-4">
    < h1>{{title}}</h1>
    < div class="row pt-4">
        < div class="col">
            <app-summary-card [storeSummary]="newCustomersCount"></app-summary-card>
        </ div>
        < div class="col">
            <app-summary-card [storeSummary]="activeUsersCount"></app-summary-card>
        </ div>
        < div class="col">
            <app-summary-card [storeSummary]="salesSum"></app-summary-card>
        </ div>
    </ div>
</ div>

Our dashboard looks like this:

storereporter-the-three-components-horizontal

Next, let us add top selling products to our dashboard. First, we create a `Product` class, mock products and a `product-card` component.

We create `/src/app/product.ts` with the content:

export class Product {
  name: string;
  rating: number;
  image: string;
  price: string;
  quantitySold: number;
  commentsCount: number;
  likesCount: number;
}

Create ‘src/app/mock-product.ts’ with the content:

import { Product } from './product';

export let PRODUCTS: Product[] = [
  {
    name: 'White Dress',
    rating: 3,
    image: 'http://res.cloudinary.com/dqracscet/image/upload/c_fill,h_720,w_506/v1495913510/fashion-model-1333640_1280_lj6dss.jpg',
    price: '147.00',
    quantitySold: 21,
    commentsCount: 28,
    likesCount: 201
  }, {
    name: 'Child Red Dress',
    rating: 4,
    image: 'http://res.cloudinary.com/dqracscet/image/upload/c_scale,h_720/v1495913604/little-girl-1143517_1280_n8qaia.jpg',
    price: '94.00',
    quantitySold: 20,
    commentsCount: 201,
    likesCount: 428
  }, {
    name: 'Chelsea Boots',
    rating: 5,
    image: 'http://res.cloudinary.com/dqracscet/image/upload/c_scale,h_720,w_506/v1495913940/boots-506830_1280_u5wsde.jpg',
    price: '20.00',
    quantitySold: 20,
    commentsCount: 6,
    likesCount: 42
  }, {
    name: 'Crop Top',
    rating: 2,
    image: 'http://res.cloudinary.com/dqracscet/image/upload/c_fill,h_720,w_506/v1495914453/asian-2307746_1280_ql35tr.jpg',
    price: '47.00',
    quantitySold: 18,
    commentsCount: 14,
    likesCount: 68
  }
];

We update `src/app/store.service.ts`:

import { PRODUCTS } from './mock-product'; // add mock products to import

...
//add getTopSellingProducts method
  getTopSellingProducts(): Promise<Product[]> {
    return Promise.resolve(PRODUCTS);
  }
...

Create the `product-card` component by right clicking on the app, select new then component. We then update the following files:

/src/app/product-card/product-card.component.html

< div class="card" *ngIf="product">
    < img class="card-img-top" src="{{ product.image }}">
    < div class="card-block">
        < p class="card-title">
            {{ product.name }} <span class="text-muted">($ {{ product.price}} )</span>
        </ p>
        < p class="card-text">
            Quantity: {{ product.quantitySold }} sold <br>
            {{ product.commentsCount }} comments | {{ product.likesCount }} likes
        < /p>
    < /div>
< /div>

/src/app/product-card/product-card.component.ts

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

import { Product } from '../product';
@Component({
  selector: 'app-product-card',
  templateUrl: './product-card.component.html',
  styleUrls: ['./product-card.component.css']
})
export class ProductCardComponent implements OnInit {
  @Input() product: Product;
  constructor() { }

  ngOnInit() {
  }

}

Finally we update `app.component.ts` and `app.component.html`:

import { Product } from './product';  \\ add to imports

topSellingProducts: Product[]; \\ add property

  // add method
  getTopSellingProducts(): void {
    this.storeService.getTopSellingProducts().then(topSellingProducts => this.topSellingProducts = topSellingProducts);
  }
  // add inside ngOnInit
  this.getTopSellingProducts();
<!-- Add inside div.container -->
< h3 class="mt-4">Top selling products</h3>
< div class="row">
    < div class="col-3 pb-4" *ngFor="let product of topSellingProducts">
        <app-product-card ="product"></app-product-card>
    </ div>
</ div>

Our final dashboard looks like this:

storereporter-final-dashboard

Ready to start using Angular IDE yourself?