Skip to content

PROJECT: Your First Angular Project - Build a Counter App

In this hands-on project, we'll use Angular to create the simplest Angular app: a counter. This tutorial will guide you through the fundamentals of Angular components, data binding, and event handling.

You can find the full source code of this project on GitHub.

What You'll Learn

  • Setting up a new Angular project
  • Creating and using components
  • Understanding data binding
  • Handling user events
  • Component communication
  • Angular best practices

Prerequisites

Make sure you have completed Module 1: Setting Up Angular Project and have Angular CLI installed.

Project Overview

We'll build a simple counter application with the following features:

  • Display a counter value
  • Increment button (+1)
  • Decrement button (-1)
  • Reset button
  • Multiple counter instances
  • Custom increment/decrement values

Let's get started! 🚀


Step 1: Introduction to the Project

Create Your Project

First, let's create a new Angular project specifically for our counter app:

ng new angular-counter-app --routing=false --style=css
cd angular-counter-app

When prompted:

  • Skip routing (we don't need it for this simple app)
  • Choose CSS for styling

Start the Development Server

ng serve --open

This will open your browser at http://localhost:4200 showing the default Angular welcome page.

Clean Up the Default Content

Let's clean up the default content in src/app/app.component.html:

<div class="container">
  <h1>My Counter App</h1>
  <p>Welcome to your first Angular project!</p>
</div>

And add some basic styling in src/app/app.component.css:

.container {
  max-width: 600px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

h1 {
  color: #1976d2;
  margin-bottom: 2rem;
}

💡 Checkpoint: Your app should now display a clean welcome message.


Step 2: Create the First Counter Component

Generate the Counter Component

ng generate component counter

This creates four files:

  • src/app/counter/counter.component.ts - Component logic
  • src/app/counter/counter.component.html - Template
  • src/app/counter/counter.component.css - Styles
  • src/app/counter/counter.component.spec.ts - Tests

Build the Counter Component

Update src/app/counter/counter.component.ts:

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

@Component({
  selector: 'app-counter',
  templateUrl: './counter.component.html',
  styleUrls: ['./counter.component.css']
})
export class CounterComponent {
  count: number = 0;

  increment(): void {
    this.count++;
  }

  decrement(): void {
    this.count--;
  }

  reset(): void {
    this.count = 0;
  }
}

Create the Counter Template

Update src/app/counter/counter.component.html:

<div class="counter-container">
  <h2>Counter: {{ count }}</h2>

  <div class="button-group">
    <button (click)="decrement()" class="btn btn-danger">-1</button>
    <button (click)="reset()" class="btn btn-secondary">Reset</button>
    <button (click)="increment()" class="btn btn-success">+1</button>
  </div>
</div>

Style the Counter

Add styles to src/app/counter/counter.component.css:

.counter-container {
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  padding: 2rem;
  margin: 1rem 0;
  background-color: #f9f9f9;
}

h2 {
  font-size: 2rem;
  margin-bottom: 1.5rem;
  color: #333;
}

.button-group {
  display: flex;
  gap: 1rem;
  justify-content: center;
  align-items: center;
}

.btn {
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: bold;
  transition: background-color 0.2s;
}

.btn-success {
  background-color: #28a745;
  color: white;
}

.btn-success:hover {
  background-color: #218838;
}

.btn-danger {
  background-color: #dc3545;
  color: white;
}

.btn-danger:hover {
  background-color: #c82333;
}

.btn-secondary {
  background-color: #6c757d;
  color: white;
}

.btn-secondary:hover {
  background-color: #5a6268;
}

Use the Counter Component

Update src/app/app.component.html to include your counter:

<div class="container">
  <h1>My Counter App</h1>
  <p>Welcome to your first Angular project!</p>

  <app-counter></app-counter>
</div>

💡 Checkpoint: You should now see a working counter with increment, decrement, and reset buttons!


Step 3: Add Multiple Counter Instances

Let's add multiple counters to see how Angular components work independently.

Update App Component

Modify src/app/app.component.html:

<div class="container">
  <h1>My Counter App</h1>
  <p>Multiple independent counters:</p>

  <app-counter></app-counter>
  <app-counter></app-counter>
  <app-counter></app-counter>
</div>

💡 Observation: Each counter maintains its own state independently. This demonstrates component encapsulation!


Step 4: Enhanced Counter with Custom Values

Let's create a more advanced counter that accepts custom increment/decrement values.

Update Counter Component with Input Properties

Modify src/app/counter/counter.component.ts:

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

@Component({
  selector: 'app-counter',
  templateUrl: './counter.component.html',
  styleUrls: ['./counter.component.css']
})
export class CounterComponent {
  @Input() initialValue: number = 0;
  @Input() step: number = 1;
  @Input() label: string = 'Counter';

  count: number = 0;

  ngOnInit(): void {
    this.count = this.initialValue;
  }

  increment(): void {
    this.count += this.step;
  }

  decrement(): void {
    this.count -= this.step;
  }

  reset(): void {
    this.count = this.initialValue;
  }
}

Update the Template

Modify src/app/counter/counter.component.html:

<div class="counter-container">
  <h2>{{ label }}: {{ count }}</h2>

  <div class="button-group">
    <button (click)="decrement()" class="btn btn-danger">-{{ step }}</button>
    <button (click)="reset()" class="btn btn-secondary">Reset</button>
    <button (click)="increment()" class="btn btn-success">+{{ step }}</button>
  </div>

  <div class="counter-info">
    <small>Step: {{ step }} | Initial: {{ initialValue }}</small>
  </div>
</div>

Add Info Styling

Add to src/app/counter/counter.component.css:

.counter-info {
  margin-top: 1rem;
  color: #666;
  font-size: 0.9rem;
}

Use Counters with Different Configurations

Update src/app/app.component.html:

<div class="container">
  <h1>My Counter App</h1>
  <p>Customizable counters with different configurations:</p>

  <app-counter
    label="Basic Counter"
    [initialValue]="0"
    [step]="1">
  </app-counter>

  <app-counter
    label="Fast Counter"
    [initialValue]="10"
    [step]="5">
  </app-counter>

  <app-counter
    label="Decimal Counter"
    [initialValue]="0"
    [step]="0.5">
  </app-counter>
</div>

💡 Key Learning: This demonstrates Angular's property binding with [property]="value" syntax!


Step 5: Adding Event Communication

Let's add the ability for counters to communicate with their parent component.

Update Counter Component with Output Events

Modify src/app/counter/counter.component.ts:

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

@Component({
  selector: 'app-counter',
  templateUrl: './counter.component.html',
  styleUrls: ['./counter.component.css']
})
export class CounterComponent {
  @Input() initialValue: number = 0;
  @Input() step: number = 1;
  @Input() label: string = 'Counter';

  @Output() valueChanged = new EventEmitter<number>();
  @Output() counterReset = new EventEmitter<void>();

  count: number = 0;

  ngOnInit(): void {
    this.count = this.initialValue;
  }

  increment(): void {
    this.count += this.step;
    this.valueChanged.emit(this.count);
  }

  decrement(): void {
    this.count -= this.step;
    this.valueChanged.emit(this.count);
  }

  reset(): void {
    this.count = this.initialValue;
    this.counterReset.emit();
    this.valueChanged.emit(this.count);
  }
}

Update App Component to Handle Events

Modify src/app/app.component.ts:

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'angular-counter-app';
  totalValue: number = 0;
  resetCount: number = 0;

  onCounterValueChanged(newValue: number): void {
    // This is a simple example - in a real app you might want to track individual counters
    console.log('Counter value changed to:', newValue);
  }

  onCounterReset(): void {
    this.resetCount++;
    console.log('A counter was reset! Total resets:', this.resetCount);
  }

  resetAllCounters(): void {
    // This would require ViewChild to access counter components
    // For now, we'll just reset our tracking
    this.resetCount = 0;
  }
}

Update App Template with Event Handlers

Modify src/app/app.component.html:

<div class="container">
  <h1>My Counter App</h1>
  <p>Counters with event communication:</p>

  <div class="stats">
    <p>Total Resets: {{ resetCount }}</p>
    <button (click)="resetAllCounters()" class="btn btn-warning">Reset Stats</button>
  </div>

  <app-counter
    label="Basic Counter"
    [initialValue]="0"
    [step]="1"
    (valueChanged)="onCounterValueChanged($event)"
    (counterReset)="onCounterReset()">
  </app-counter>

  <app-counter
    label="Fast Counter"
    [initialValue]="10"
    [step]="5"
    (valueChanged)="onCounterValueChanged($event)"
    (counterReset)="onCounterReset()">
  </app-counter>
</div>

Add Stats Styling

Add to src/app/app.component.css:

.stats {
  background-color: #e3f2fd;
  padding: 1rem;
  border-radius: 4px;
  margin: 1rem 0;
}

.btn-warning {
  background-color: #ffc107;
  color: #212529;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.btn-warning:hover {
  background-color: #e0a800;
}

💡 Key Learning: This demonstrates Angular's event binding with (event)="handler($event)" syntax!


Step 6: Challenges for You to Practice Angular

Now that you have a working counter app, try these challenges to deepen your Angular knowledge:

Challenge 1: Add Validation 🎯

Difficulty: Beginner

Add input validation to prevent the counter from going below 0 or above 100.

Hints:

  • Add min/max input properties
  • Disable buttons when limits are reached
  • Add visual feedback for disabled state

Challenge 2: Counter History 📈

Difficulty: Intermediate

Add a history feature that tracks all counter changes.

Requirements:

  • Show last 5 operations (increment/decrement/reset)
  • Display timestamp for each operation
  • Add an "Undo" button

Hints:

  • Create an interface for history entries
  • Use an array to store history
  • Consider using Angular's DatePipe

Challenge 3: Local Storage Persistence 💾

Difficulty: Intermediate

Make counter values persist across browser sessions.

Requirements:

  • Save counter state to localStorage
  • Restore state on app reload
  • Add a "Clear Storage" button

Hints:

  • Use ngOnDestroy to save state
  • Use ngOnInit to restore state
  • Handle JSON serialization

Challenge 4: Counter Groups 👥

Difficulty: Advanced

Create groups of counters with group-level operations.

Requirements:

  • Create counter groups with names
  • Add/remove counters from groups
  • Group-level reset and statistics
  • Calculate group totals

Hints:

  • Create a service for group management
  • Use component communication patterns
  • Consider using Angular forms for group names

Challenge 5: Animated Counters 🎨

Difficulty: Advanced

Add animations to make the counter more engaging.

Requirements:

  • Animate number changes
  • Add button click animations
  • Smooth transitions for state changes

Hints:

  • Use Angular Animations API
  • Consider CSS transitions
  • Add loading states for operations

Solution Examples

Challenge 1 Solution: Basic Validation

// In counter.component.ts
@Input() minValue: number = 0;
@Input() maxValue: number = 100;

get canIncrement(): boolean {
  return this.count < this.maxValue;
}

get canDecrement(): boolean {
  return this.count > this.minValue;
}

increment(): void {
  if (this.canIncrement) {
    this.count += this.step;
    this.valueChanged.emit(this.count);
  }
}
<!-- In counter.component.html -->
<button
  (click)="decrement()"
  [disabled]="!canDecrement"
  class="btn btn-danger">
  -{{ step }}
</button>

Next Steps

Congratulations! 🎉 You've built your first Angular application with:

  • Component creation and organization
  • Data binding (interpolation, property, event)
  • Component communication (Input/Output)
  • Event handling
  • Basic styling

What to Learn Next

  1. Angular Services - Move counter logic to a shared service
  2. Angular Forms - Add form inputs for dynamic counter configuration
  3. Angular Routing - Create multiple pages for your app
  4. HTTP Client - Save counters to a backend API
  5. Angular Material - Use professional UI components

Additional Resources

Keep building and experimenting! 🚀


Project completed! You now have a solid foundation in Angular fundamentals. The counter app may be simple, but it demonstrates the core concepts you'll use in every Angular application.