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