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:
When prompted:
- Skip routing (we don't need it for this simple app)
- Choose CSS for styling
Start the Development Server¶
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¶
This creates four files:
src/app/counter/counter.component.ts- Component logicsrc/app/counter/counter.component.html- Templatesrc/app/counter/counter.component.css- Stylessrc/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:
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¶
- Angular Services - Move counter logic to a shared service
- Angular Forms - Add form inputs for dynamic counter configuration
- Angular Routing - Create multiple pages for your app
- HTTP Client - Save counters to a backend API
- 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.