Personal Budget Manager with Angular

This step-by-step guide will teach you how to create a Personal Budget Manager App using Angular, featuring Income, Expense and Current Balance.

Step 1 – Create Angular App

Run the following command to create a new Angular project:

ng new personal-budget-manager --no-standalone --routing --ssr=false

Run the following command to install jquery, popperjs, and bootstrap :

cd personal-budget-manager

npm install jquery --save
npm install @popperjs/core --save
npm install ngx-bootstrap bootstrap@latest
ng add ngx-bootstrap

Run the following command to install Angular Ant Design:

ng add ng-zorro-antd

Your Application Folder Structure will be like:

src
├── app
│   ├── home
│   │   ├── home.component.html
│   │   ├── home.component.css
│   │   └── home.component.ts
│   ├── model
│   │   └── transaction.ts
│   ├── service
│   │   └── data.service.ts
│   ├── transaction
│   │   ├── transaction.component.html
│   │   ├── transaction.component.css
│   │   └── transaction.component.ts
│   ├── app.component.html
│   ├── app.component.ts
│   ├── app.module.ts
│   ├── app-routing.module.ts	
├── index.html
├── style.css

Step 2 – Project Setup

Copy and Paste following code styles section of angular.json file:

"./node_modules/bootstrap/dist/css/bootstrap.min.css"

						

Copy and Paste following code script section of angular.json file:

"./node_modules/jquery/dist/jquery.min.js",
"./node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"

						

Step 3 – Create Home Component for Dashboard

Create Home Component for Dashboard with the following command:

ng generate component --skip-tests=true home

You will get:

home.component.html
home.component.css
home.component.ts
						

Copy and Paste the following code to home.component.html:

<div class="container mt-5">
  <h3 class="text-center">Welcome to Personal Budget Manager</h3>
  <p class="lead text-center">Manage your Income and Expense Here</p>
  <div class="row text-center mt-4">
    <div class="col-md-4">
      <div class="card">
        <div class="card-income">
          <h5>Total Income</h5>
          <h2>Tk {{ totalIncome }}</h2>
        </div>
      </div>
    </div>
    <div class="col-md-4">
      <div class="card">
        <div class="card-expense">
          <h5>Total Expense</h5>
          <h2>Tk {{ totalExpense }}</h2>
        </div>
      </div>
    </div>
    <div class="col-md-4">
      <div class="card">
        <div class="card-balance">
          <h5>Balance</h5>
          <h2>Tk {{ balance }}</h2>
        </div>
      </div>
    </div>
  </div>
</div>
 

Copy and Paste the following code to home.component.ts :

 import { Component } from '@angular/core';
import {DataService} from "../services/data.service";
@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrl: './home.component.css'
})
export class HomeComponent {
  totalIncome: number = 0;
  totalExpense: number = 0;
  balance: number = 0;

  constructor(private dataService: DataService) {
    this.calculateSummary();
  }

  calculateSummary() {
    const transactions = this.dataService.getTransactions();
    this.totalIncome = transactions
      .filter((t) => t.type === 'income')
      .reduce((sum, t) => sum + t.amount, 0);
    this.totalExpense = transactions
      .filter((t) => t.type === 'expense')
      .reduce((sum, t) => sum + t.amount, 0);
    this.balance = this.totalIncome - this.totalExpense;
  }
}
 

Copy and Paste the following code to app.component.html for Navigation Menu:

 <nav class="navbar navbar-expand-lg sticky-top bg-white">
  <div class="container">
    <a class="navbar-brand" routerLink="/">Budget Manager</a>
    <button
      class="navbar-toggler"
      type="button"
      data-bs-toggle="collapse"
      data-bs-target="#navbarNav"
      aria-controls="navbarNav"
      aria-expanded="false"
      aria-label="Toggle navigation"
    >
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav ms-auto">
        <li class="nav-item">
          <a class="nav-link" routerLink="/">Dashboard</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" routerLink="/transaction">Transactions</a>
        </li>
      </ul>
    </div>
  </div>
</nav>
<div class="bg-light py-2">
  <div class="container text-center">
    <!-- <h3 class="text-primary">Manage Your Income & Expenses Efficiently with Ease!</h3> -->
  </div>
</div> 

Copy and Paste the following code to app-routing.module.ts at import section:

 import { HomeComponent} from "./home/home.component"; 

Copy and Paste the following code to app-routing.module.ts at const routes: Routes section:

 { path: '', component: HomeComponent }, 

Add following code to app.component.html for Routing to Dashboard:

 <router-outlet></router-outlet> 

Step 4 – Create Model for Transaction

Run following Command to create model for Transaction:

 ng g i model/transaction 

You will get transaction.ts file in model directory:

Copy and Paste the following code to transaction.ts:

 export interface Transaction {
  id: number;
  date: string;
  description: string;
  amount: number;
  type: 'income' | 'expense' | 'placeholder';
}

Step 5 – Create Service for Transaction

Run following Command to create data-service to Implement Logic for Transaction:

 ng generate service services/data --skip-tests 

You will get data.service.ts.
Copy and Paste following code to data.service.ts for Transaction data logic:

 import { Injectable } from '@angular/core';
import {Transaction} from "../model/transaction";

@Injectable({
  providedIn: 'root',
})
export class DataService {
  private storageKey = 'transactions';

  getTransactions(): Transaction[] {
    const data = localStorage.getItem(this.storageKey);
    return data ? JSON.parse(data) : [];
  }

  saveTransaction(transaction: Transaction) {
    const transactions = this.getTransactions();
    transactions.push(transaction);
    localStorage.setItem(this.storageKey, JSON.stringify(transactions));
  }

  deleteTransaction(id: number) {
    const transactions = this.getTransactions().filter((t) => t.id !== id);
    localStorage.setItem(this.storageKey, JSON.stringify(transactions));
  }
} 

Step 6 – Create Transaction Component

Copy and Paste the following code :

 ng generate component --skip-tests=true transaction 

You will get:

transaction.component.html
transaction.component.css
transaction.component.ts
						

Copy and Paste the following code to transaction.component.html :

 <div class="container mt-5">
  <h2>Transactions</h2>
  <form (submit)="addTransaction()" class="mb-4">
    <div class="row g-2">
      <div class="col-md-3 col-sm-6">
        <input
          type="date"
          [(ngModel)]="newTransaction.date"
          name="date"
          class="form-control"
          placeholder="Date"
        />
      </div>
      <div class="col-md-3 col-sm-6">
        <input
          type="text"
          [(ngModel)]="newTransaction.description"
          name="description"
          class="form-control"
          placeholder="Description"
        />
      </div>
      <div class="col-md-2 col-sm-6">
        <input
          type="number"
          [(ngModel)]="newTransaction.amount"
          name="amount"
          class="form-control"
          placeholder="Amount"
        />
      </div>
      <div class="col-md-2 col-sm-6">
     <select
    [(ngModel)]="newTransaction.type"
    name="type"
    class="form-control"
    required
    >
    <option value="placeholder" disabled>Select income type</option> <!-- Placeholder -->
    <option value="income">Income</option>
    <option value="expense">Expense</option>
    </select>
    </div>
      <div class="col-md-2 col-sm-12">
        <button type="submit" class="neumorphic-btn w-100">Add</button>
      </div>
    </div>
  </form>

  <!-- Responsive Table -->
  <div class="table-responsive">
    <table class="table table-bordered">
      <thead class="thead-dark">
      <tr>
        <th>Date</th>
        <th>Description</th>
        <th>Amount</th>
        <th>Type</th>
        <th>Actions</th>
      </tr>
      </thead>
      <tbody>
      <tr *ngFor="let transaction of transactions">
        <td>{{ transaction.date }}</td>
        <td>{{ transaction.description }}</td>
        <td>Tk {{ transaction.amount }}</td>
        <td>{{ transaction.type }}</td>
        <td>
          <button
            class="neumorphic-btn delete"
            (click)="deleteTransaction(transaction.id)"
          >
            Delete
          </button>
        </td>
      </tr>
      </tbody>
    </table>
  </div>
</div> 

Copy and Paste the following code to transaction.component.ts :

 import { Component } from '@angular/core';
import {Transaction} from "../model/transaction";
import {DataService} from "../services/data.service";


@Component({
  selector: 'app-transaction',
  templateUrl: './transaction.component.html',
})
export class TransactionsComponent {
  transactions: Transaction[] = [];
  newTransaction: Partial = {
    type: 'placeholder', // Set initial placeholder value
    date: this.getCurrentDate(),
    amount: undefined,
  };

  constructor(private dataService: DataService) {
    this.transactions = this.dataService.getTransactions();
  }

  // Helper method to get the current date in YYYY-MM-DD format
  private getCurrentDate(): string {
    return new Date().toISOString().split('T')[0];
  }

  addTransaction() {
    // Check that 'type' is not the placeholder value
    if (
      this.newTransaction.date &&
      this.newTransaction.amount &&
      this.newTransaction.type &&
      this.newTransaction.type !== 'placeholder'
    ) {
      const transaction: Transaction = {
        id: Date.now(),
        ...this.newTransaction,
      } as Transaction;
      this.dataService.saveTransaction(transaction);
      this.transactions.push(transaction);

      // Reset `newTransaction`
      this.newTransaction = {
        type: 'placeholder', // Reset to placeholder
        date: this.getCurrentDate(),
      };
    }
  }

  deleteTransaction(id: number) {
    this.dataService.deleteTransaction(id);
    this.transactions = this.transactions.filter((t) => t.id !== id);
  }
} 

Copy and Paste the following code to app-routing.module.ts at import section:

import { TransactionComponent } from "./transaction/transaction.component";

Copy and Paste the following code to app-routing.module.ts "const routes: Routes" section:

{ path: 'transaction', component: TransactionComponent },

Step 7 – Add CSS

Copy and Paste the following CSS code to style.css:

 /* Container Spacing */
.button-container {
  margin-top: 50px;
}
.container {
  margin: auto;
  padding: 20px;
}
form div {
  margin-bottom: 10px;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  margin: 5px 0;
  display: flex;
  justify-content: space-between;
}
body {
  font-family: 'Roboto', sans-serif;
  background-color: #f8f9fa;
}

.card-income {
  background: #e6f7e6; /* Light green for income */
  border: 1px solid #b3ddb3;
  box-shadow: 6px 6px 12px #b3ddb3, -6px -6px 12px #ffffff;
  color: #2d862d; /* Dark green text */
  padding: 0.7em 1.7em;
  border-radius: 0.5em;
}

.card-expense {
  background: #fdecea; /* Light red for expense */
  border: 1px solid #f5c2c0;
  box-shadow: 6px 6px 12px #f5c2c0, -6px -6px 12px #ffffff;
  color: #d93025; /* Dark red text */
  padding: 0.7em 1.7em;
  border-radius: 0.5em;
}

.card-balance {
  background: #eaf4fd; /* Light blue for balance */
  border: 1px solid #c2ddf5;
  box-shadow: 6px 6px 12px #c2ddf5, -6px -6px 12px #ffffff;
  color: #2a65d1; /* Dark blue text */
  padding: 0.7em 1.7em;
  border-radius: 0.5em;
}
.card-income:hover {
  background: #d9f0d9; /* Slightly lighter green on hover */
}
.card-balance:hover {
  background: #dcecff; /* Slightly lighter blue on hover */
}
.card-expense:hover {
  background: #f9dbd9; /* Slightly lighter red on hover */
}
.navbar {
  box-shadow: 6px 6px 12px #c5c5c5, -6px -6px 12px #ffffff;
}
.neumorphic-btn {
  color: #090909;
  padding: 0.7em 1.7em;
  font-size: 13px;
  border-radius: 0.5em;
  background: #f1f1f1;
  cursor: pointer;
  border: 1px solid #e8e8e8;
  transition: all 0.3s;
  box-shadow: 6px 6px 12px #c5c5c5, -6px -6px 12px #ffffff;
}

.neumorphic-btn.delete {
  color: white;
  background: #ff4d4d; /* Red background */
  border: 1px solid #d43f3f; /* Darker red border */
  box-shadow: 2px 2px 4px #c03838, -2px -2px 4px #ff6a6a; /* Red shadows */
  padding: 0.3em 1em; /* Smaller padding */
  font-size: 12px; /* Slightly smaller font */
  border-radius: 0.3em; /* Smaller border radius */
  transition: all 0.2s;
}

.neumorphic-btn.delete:hover {
  background: #e60000; /* Darker red on hover */
  box-shadow: 3px 3px 9px #b30000, -3px -3px 9px #ff3333;
}

.neumorphic-btn:hover {
  border: 1px solid white;
}

.neumorphic-btn:active {
  box-shadow: 4px 4px 12px #c5c5c5, -4px -4px 12px #ffffff;
}
.navbar-brand {
  font-size: 1.5rem;
  font-weight: bold;
}

.nav-link {
  font-size: 1rem;
  margin: 0 5px;
}

.nav-link.active {
  font-weight: bold;
  text-decoration: underline;
}
.lead {
  font-size: 1.2rem;
  color: #6c757d;
}
/* Responsive Design for Small Screens */
@media (max-width: 768px) {
  .neumorphic-btn {
    font-size: 13px;
    padding: 0.6em 1.4em;
  }

  .button-container {
    margin-top: 30px;
  }
  .card {
    margin-top: 30px;
  }
}
@media (max-width: 768px) {
  table th, table td {
    font-size: 12px;
    padding: 8px;
  }
}