In this angular tutorial we are going to create a dashboard for an Ecommerce store, the dashboard 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 .

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

Store summary cards

We are going to add three cards to our dashboard, 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.



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

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



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



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

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]="product"></app-product-card> </ div> </ div>

Our final dashboard looks like this

