Making Amazon's card component with Angular

Making Amazon's card component with Angular

Introduction

If you have seen Amazon you will notice that they use cards quite a lot. Infacts cards are a common feature of many websites, and it makes sense if we make it a component thus increasing reusability. image.png

In this tutorial we will try to replicate the same using Angular, and in the process will cover a few topics

Concepts Covered -

  • Creating components
  • Interpolation and property binding
  • ngIf & ngFor directives
  • Passing data from parent to child component

Before you get overwhelmed, lets get a gist of these concepts in following section


Concepts that will be used

Interpolation

Text interpolation lets you incorporate dynamic string values into your HTML templates i.e. Get data from class to template Example -

// In app.component.ts
title = 'Hello World';

// In app.component.html
<h1>{{title}}</h1> // insert between html tags
<h1 innerText={{title}}> // Or in html attribute

Property binding

Property binding in Angular helps you set values for properties of HTML elements or directives. We can use property binding to implement toggle button functionality, set paths programmatically, and share values between components. Most of the time interpolation and property binding can achieve same thing.

<img [src]='product.imageUrl'> // Write template expn inside quotes
<img src={{'home/products/'+product.imageUrl}}> Interpolation is usefull in longer expns

ngIf & ngFor directives

Directives are classes that add additional behavior to elements in your Angular applications. There are 3 type of directives - Components, Attribute directives (NgClass,NgStyle) , Structural directives (NgFor, NgSwitch)

//ngIf example
<div *ngIf='products.length'; else ElsePart>Display if part</div>
<ng-template #ElsePart>Something else</ng-template>

//ngfor example
<ul>
    <li *ngFor="let product of products">{{product.name}}</li>
</ul>

Passing data from parent to child

Using @Input decorator in a child component or directive we can signify that the property can receive its value from its parent component.

// Inside parent.component.html
<child [data]='someData'></child>

// Inside child.component.ts
export class ChildComponent{
   @Input() data: string;
}

Setting up project

First of lets create a project, we will be using Angular CLI( a command line tool) for helping us create a project

// Create a new project
ng new cards

// Create a component card
ng g c card

We create a card component, for the purpose of reusability, now everytime we need card we can use in app or any other component. Our project structure should look something like this -

image.png

Note on Dynamic data

Now supposedly we will get data from some api/file in parent(app) component and we will have to pass it to child(card) component. For simplicity of this tutorial, let's say we have receive data in following format and need to pass this to card component.

posts = [
    {
      title: 'card title ',
      links: ['#','#',..],
      imageUrls: ['url1','url2',..],
      imageCaptions: ['caption1','caption2',..]
    }, .. ]

Working on Application

Now we have enough conceptual knowledge to create cards.

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {
  // data we will pass from parent to child (In real world, will get from api)
  posts = [
    {
      title: 'Amazon Pay | Pay utility bills fast & conveniently',
      links: ['#','#','#','#'],
      imageUrls: [
        'https://images-eu.ssl-images-amazon.com/images/G/31/img19/AmazonPay/Kartik/FEBGTM/Electricity_186x116._SY116_CB646259375_.jpg',
        'https://images-eu.ssl-images-amazon.com/images/G/31/img19/AmazonPay/Kartik/FEBGTM/LPG_186x116._SY116_CB646259375_.jpg',
        'https://images-eu.ssl-images-amazon.com/images/G/31/img19/AmazonPay/Kartik/FEBGTM/Broadband_186x116._SY116_CB646259375_.jpg', 
        'https://images-eu.ssl-images-amazon.com/images/G/31/img19/AmazonPay/Kartik/FEBGTM/PCrevised/DTH_186x116._SY116_CB628894454_.jpg'],
      imageCaptions: ['Electricity Bill','LPG Gas cylinder','Broadband bill', 'DTH Recharge']
    },

    {
      title: 'Amazon Pay | Book your travel tickets',
      links: ['#','#','#','#'],
      imageUrls: [
        'https://images-eu.ssl-images-amazon.com/images/G/31/img16/AmazonPayTravels/November/GWPercolate/Flight_186x116._SY116_CB653435429_.jpg',
        'https://images-eu.ssl-images-amazon.com/images/G/31/img16/AmazonPayTravels/November/GWPercolate/Bus_186x116._SY116_CB653435429_.jpg',
        'https://images-eu.ssl-images-amazon.com/images/G/31/img16/AmazonPayTravels/November/GWPercolate/Train_186x116._SY116_CB653435429_.jpg', 
        'https://images-eu.ssl-images-amazon.com/images/G/31/img16/AmazonPayTravels/November/GWPercolate/essential_186x116._SY116_CB653435429_.jpg'],
      imageCaptions: ['Flight Tickets', 'Bus Tickets', 'Train Tickets', 'Essenbtial Travel products']
    }
  ]
}

app.component.html

<app-card 
    *ngFor="let post of posts" 
    [title]="post.title" 
    [imageUrls]="post.imageUrls"
    [imageCaptions]="post.imageCaptions"
    [links]="post.links"
  ></app-card>

card.component.ts

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

@Component({
  selector: 'app-card',
  templateUrl: './card.component.html',
  styleUrls: ['./card.component.css']
})
export class CardComponent implements OnInit {
  // Expecting data from parent
  @Input() title='';
  @Input() imageUrls: string[] = [];
  @Input() imageCaptions: string[] = [];
  @Input() links: string[] = [];
  constructor() { }

  ngOnInit(): void {
  }
}

card.component.html

<div class="card">
  <h1>{{title}}</h1>
  <div *ngFor="let img of imageUrls; let i=index;" class="card-box">
    <a [href]="links[i]">
    <img [src]="img" >
    <p *ngIf="imageCaptions[i]; else defaultCaption">{{imageCaptions[i]}}</p>
    <ng-template #defaultCaption>No caption available</ng-template>
    </a>
  </div>
</div>

Styling the application

app.component.css

:host {
  display: flex;
  justify-content: center;
}

card.component.css

.card {
  max-width: 350px;
  height: 420px;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  border-radius: 2px;
  margin: 10px;
  box-shadow: 7px 3px 11px -7px rgba(0, 0, 0, 0.75);
  -webkit-box-shadow: 7px 3px 11px -7px rgba(0, 0, 0, 0.75);
  -moz-box-shadow: 7px 3px 11px -7px rgba(0, 0, 0, 0.75);
}

.card h1 {
  text-align: center;
  font-size: 1.1rem;
  font-weight: 700;
  height: 30px;
  padding: 5px;
}

.card-box {
  width: 165px;
  padding: 8px;
}

.card-box img {
  width: 100%;
}

.card-box p {
  font-size: 12px;
}

.card-box a {
  text-decoration: none;
  color: black;
}

.card-box a:hover {
  text-decoration: none;
  color: rgb(87, 87, 87);
  filter: opacity(90%);
}

Final result

image.png

Did you find this article valuable?

Support Arjit Sharma by becoming a sponsor. Any amount is appreciated!