In this tutorial, let’s learn how to create reusable components in Angular. I’ll be making use of Angular’s @Input and @Output directives.

Source code from this tutorial is available at GitHub.

You can also follow the video tutorial on YouTube.

Creating an Angular App

Assuming that you already have Angular CLI installed, create an Angular app using the CLI.

ng new reusable-comp

Once you have the boilerplate project created, you need to install Bootstrap in your application.

Add the following code to the app.component.html file.

<div class="container">
    <div class="main">
        <div class="py-5 text-center">
            <h2>Checkout form</h2>
            <p class="lead">Below is an example form built entirely with Bootstrap’s form controls. Each required form
                group has
                a validation state that can be triggered by attempting to submit the form without completing it.</p>
        </div>
        <div class="row g-5">
            <div class="col-md-5 col-lg-4 order-md-last">
                <h4 class="d-flex justify-content-between align-items-center mb-3">
                    <span class="text-primary">Your cart</span>
                    <span class="badge bg-primary rounded-pill">3</span>
                </h4>
                <ul class="list-group mb-3">
                    <li class="list-group-item d-flex justify-content-between lh-sm">
                        <div>
                            <h6 class="my-0">Product name</h6>
                            <small class="text-muted">Brief description</small>
                        </div>
                        <span class="text-muted">$12</span>
                    </li>
                    <li class="list-group-item d-flex justify-content-between lh-sm">
                        <div>
                            <h6 class="my-0">Second product</h6>
                            <small class="text-muted">Brief description</small>
                        </div>
                        <span class="text-muted">$8</span>
                    </li>
                    <li class="list-group-item d-flex justify-content-between lh-sm">
                        <div>
                            <h6 class="my-0">Third item</h6>
                            <small class="text-muted">Brief description</small>
                        </div>
                        <span class="text-muted">$5</span>
                    </li>
                    <li class="list-group-item d-flex justify-content-between bg-light">
                        <div class="text-success">
                            <h6 class="my-0">Promo code</h6>
                            <small>EXAMPLECODE</small>
                        </div>
                        <span class="text-success">−$5</span>
                    </li>
                    <li class="list-group-item d-flex justify-content-between">
                        <span>Total (USD)</span>
                        <strong>$20</strong>
                    </li>
                </ul>
                <form class="card p-2">
                    <div class="input-group">
                        <input type="text" class="form-control" placeholder="Promo code">
                        <button type="submit" class="btn btn-secondary">Redeem</button>
                    </div>
                </form>
            </div>
            <div class="col-md-7 col-lg-8">
                <h4 class="mb-3">Billing address</h4>
                <form class="needs-validation" novalidate>
                    <div class="row g-3">
                        <div class="col-sm-6">
                            <label for="firstName" class="form-label">First name</label>
                            <input type="text" class="form-control" id="firstName" placeholder="" value="" required>
                            <div class="invalid-feedback">
                                Valid first name is required.
                            </div>
                        </div>

                        <div class="col-sm-6">
                            <label for="lastName" class="form-label">Last name</label>
                            <input type="text" class="form-control" id="lastName" placeholder="" value="" required>
                            <div class="invalid-feedback">
                                Valid last name is required.
                            </div>
                        </div>

                        <div class="col-12">
                            <label for="username" class="form-label">Username</label>
                            <div class="input-group has-validation">
                                <span class="input-group-text">@</span>
                                <input type="text" class="form-control" id="username" placeholder="Username" required>
                                <div class="invalid-feedback">
                                    Your username is required.
                                </div>
                            </div>
                        </div>

                        <div class="col-12">
                            <label for="email" class="form-label">Email <span
                                    class="text-muted">(Optional)</span></label>
                            <input type="email" class="form-control" id="email" placeholder="you@example.com">
                            <div class="invalid-feedback">
                                Please enter a valid email address for shipping updates.
                            </div>
                        </div>

                        <div class="col-12">
                            <label for="address" class="form-label">Address</label>
                            <input type="text" class="form-control" id="address" placeholder="1234 Main St" required>
                            <div class="invalid-feedback">
                                Please enter your shipping address.
                            </div>
                        </div>

                        <div class="col-12">
                            <label for="address2" class="form-label">Address 2 <span
                                    class="text-muted">(Optional)</span></label>
                            <input type="text" class="form-control" id="address2" placeholder="Apartment or suite">
                        </div>

                    </div>

                    <hr class="my-4">

                    <div class="form-check">
                        <input type="checkbox" class="form-check-input" id="same-address">
                        <label class="form-check-label" for="same-address">Shipping address is the same as my billing
                            address</label>
                    </div>

                    <div class="form-check">
                        <input type="checkbox" class="form-check-input" id="save-info">
                        <label class="form-check-label" for="save-info">Save this information for next time</label>
                    </div>

                    <hr class="my-4">

                    <h4 class="mb-3">Payment</h4>

                    <div class="my-3">
                        <div class="form-check">
                            <input id="credit" name="paymentMethod" type="radio" class="form-check-input" checked
                                required>
                            <label class="form-check-label" for="credit">Credit card</label>
                        </div>
                        <div class="form-check">
                            <input id="debit" name="paymentMethod" type="radio" class="form-check-input" required>
                            <label class="form-check-label" for="debit">Debit card</label>
                        </div>
                        <div class="form-check">
                            <input id="paypal" name="paymentMethod" type="radio" class="form-check-input" required>
                            <label class="form-check-label" for="paypal">PayPal</label>
                        </div>
                    </div>

                    <div class="row gy-3">
                        <div class="col-md-6">
                            <label for="cc-name" class="form-label">Name on card</label>
                            <input type="text" class="form-control" id="cc-name" placeholder="" required>
                            <small class="text-muted">Full name as displayed on card</small>
                            <div class="invalid-feedback">
                                Name on card is required
                            </div>
                        </div>

                        <div class="col-md-6">
                            <label for="cc-number" class="form-label">Credit card number</label>
                            <input type="text" class="form-control" id="cc-number" placeholder="" required>
                            <div class="invalid-feedback">
                                Credit card number is required
                            </div>
                        </div>

                        <div class="col-md-3">
                            <label for="cc-expiration" class="form-label">Expiration</label>
                            <input type="text" class="form-control" id="cc-expiration" placeholder="" required>
                            <div class="invalid-feedback">
                                Expiration date required
                            </div>
                        </div>

                        <div class="col-md-3">
                            <label for="cc-cvv" class="form-label">CVV</label>
                            <input type="text" class="form-control" id="cc-cvv" placeholder="" required>
                            <div class="invalid-feedback">
                                Security code required
                            </div>
                        </div>
                    </div>

                    <hr class="my-4">

                    <button class="w-100 btn btn-primary btn-lg" type="submit">Continue to checkout</button>
                </form>
            </div>
        </div>
    </div>
</div>

Add the following CSS to app.component.css.

.main{
     margin: 50px;
}

Save the changes and run the Angular application. You will have a screen like:

enter image description here

On the right side of the above screen, you can see a Your cart section. Let’s make it as a separate reusable Angular component.

Creating Reusable Angular Component

Create a new Angular component using the Angular CLI.

ng g component cart

Remove the Your cart HTML portion from app.component.html and add to cart.component.html file.

<div>
    <h4 class="d-flex justify-content-between align-items-center mb-3">
        <span class="text-primary">Your cart</span>
        <span class="badge bg-primary rounded-pill">3</span>
    </h4>
    <ul class="list-group mb-3">
        <li class="list-group-item d-flex justify-content-between lh-sm">
            <div>
                <h6 class="my-0">Product name</h6>
                <small class="text-muted">Brief description</small>
            </div>
            <span class="text-muted">$12</span>
        </li>
        <li class="list-group-item d-flex justify-content-between lh-sm">
            <div>
                <h6 class="my-0">Second product</h6>
                <small class="text-muted">Brief description</small>
            </div>
            <span class="text-muted">$8</span>
        </li>
        <li class="list-group-item d-flex justify-content-between lh-sm">
            <div>
                <h6 class="my-0">Third item</h6>
                <small class="text-muted">Brief description</small>
            </div>
            <span class="text-muted">$5</span>
        </li>
        <li class="list-group-item d-flex justify-content-between bg-light">
            <div class="text-success">
                <h6 class="my-0">Promo code</h6>
                <small>EXAMPLECODE</small>
            </div>
            <span class="text-success">−$5</span>
        </li>
        <li class="list-group-item d-flex justify-content-between">
            <span>Total (USD)</span>
            <strong>$20</strong>
        </li>
    </ul>

    <form class="card p-2">
        <div class="input-group">
            <input type="text" class="form-control" placeholder="Promo code">
            <button type="submit" class="btn btn-secondary">Redeem</button>
        </div>
    </form>
</div>

Now inside app.component.html file, add the app-cart component. Here is how the modified app.component.html file looks:

<div class="container">
  <div class="main">
    <div class="py-5 text-center">
      <h2>Checkout form</h2>
      <p class="lead">Below is an example form built entirely with Bootstrap’s form controls. Each required form group has
        a validation state that can be triggered by attempting to submit the form without completing it.</p>
    </div>
    <div class="row g-5">
      <div class="col-md-5 col-lg-4 order-md-last">
        <app-cart></app-cart>
      </div>
      <div class="col-md-7 col-lg-8">
         <!-- HTML Code removed for brevity. -->
      </div>
    </div>
  </div>
</div>

Passing Data Using @Input

Inside the cart component the data displayed is static data. Let’s pass some data to CartComponent using @Input directive. Define an @Input directive in cart.component.ts file:

@Input() products : Product[];

Product type seen above is a model defined inside app/model/product.ts

export class Product{
    public name : String;
    public description : String;
    public price : number;

    constructor(name, desc, price){
        this.name = name;
        this.description = desc;
        this.price = price;
    }
}

Now let’s define some products in the app.component.ts file. Import Product model and define an array and push some dummy data.

import { Component, OnInit } from '@angular/core';
import { Product } from '../app/model/product';

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

  products : Product[] = [];

  ngOnInit(){
    this.products.push(new Product('Gillete Razor','Shaving Razor', 100));
    this.products.push(new Product('Lenovo Laptop','Laptop', 2000));
    this.products.push(new Product('Trimmer','Hair trimmer', 2500));
  }
}

Now let’s pass the products array to the CartComponent from app.component.html.

<app-cart [products]="products"></app-cart>

Modify the cart.component.html code to iterate over the products and render the products.

<div>
  <h4 class="d-flex justify-content-between align-items-center mb-3">
    <span class="text-primary">Your cart</span>
    <span class="badge bg-primary rounded-pill">3</span>
  </h4>
  <ul class="list-group mb-3">
    <li *ngFor="let product of products" class="list-group-item d-flex justify-content-between lh-sm">
      <div>
        <h6 class="my-0"></h6>
        <small class="text-muted"></small>
      </div>
      <span class="text-muted">$</span>
    </li>
    <li class="list-group-item d-flex justify-content-between bg-light">
      <div class="text-success">
        <h6 class="my-0">Promo code</h6>
        <small>EXAMPLECODE</small>
      </div>
      <span class="text-success">−$5</span>
    </li>
    <li class="list-group-item d-flex justify-content-between">
      <span>Total (USD)</span>
      <strong>$20</strong>
    </li>
  </ul>

  <form class="card p-2">
    <div class="input-group">
      <input type="text" class="form-control" placeholder="Promo code">
      <button type="submit" class="btn btn-secondary">Redeem</button>
    </div>
  </form>
</div>

Save the changes and restart the Angular application. You’ll be able to see the passed in data being rendered in the cart section.

enter image description here

Emitting Data Using @Output

Since it’s a re usable component, it should input and emit data. You can emit data using the @Output Angular directive. Define a @Output emitter inside the cart.component.ts file.

@Output() priceEmitter = new  EventEmitter<Number>();

Let’s define a method to calculate the sum of the products and emit it back to the parent component.

  calculatePrice(){
    for(let i = 0; i < this.products.length; i++){
      this.totalPrice += this.products[i]['price']
    }
    this.priceEmitter.emit(this.totalPrice);
  }

Here is how the complete cart.component.ts file looks:

import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Product } from '../model/product';


@Component({
  selector: 'app-cart',
  templateUrl: './cart.component.html',
  styleUrls: ['./cart.component.css']
})
export class CartComponent implements OnInit {

  totalPrice : number = 0;
  @Input() products : Product[];
  @Output() priceEmitter = new EventEmitter<Number>();

  constructor() { }

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

  calculatePrice(){
    for(let i = 0; i < this.products.length; i++){
      this.totalPrice += this.products[i]['price']
    }
    this.priceEmitter.emit(this.totalPrice);
  }
}

To capture the data from the emitter event, you need to define a method inside app.component.ts.

priceEmitter(value){
   console.log('total sum is ', value);
}

Let’s bind the parent method priceEmitter in app.component.ts to child’s event.

<app-cart  (priceEmitter)="priceEmitter($event)"  [products]="products"></app-cart>

Save the above changes and you will be able to see the total price emitted to the parent component and logged in the browser console.

Source Code

In this tutorial, you learnt how to create a reusable Angular component using the @Input and @Ouput Angular directives.

Source code from this tutorial can be found at GitHub.