Exercise 1: Standalone Component Basics
Objective
Create your first standalone component and understand the fundamental differences from NgModule-based components.
Prerequisites
- Node.js 18+ installed
- Angular CLI 15+ installed
Steps
1. Create a New Standalone Component
# Create a new standalone component using CLI
ng generate component user-card --standalone
2. Examine the Generated Component
// user-card.component.ts
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
@Component({
selector: "app-user-card",
standalone: true,
imports: [CommonModule],
templateUrl: "./user-card.component.html",
styleUrls: ["./user-card.component.css"],
})
export class UserCardComponent {}
Notice:
standalone: trueflagimportsarray instead of being declared in NgModule- No need for NgModule registration
3. Add Component Logic
import { Component } from "@angular/core";
import { CommonModule } from "@angular/common";
@Component({
selector: "app-user-card",
standalone: true,
imports: [CommonModule],
template: `
<div class="user-card">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<span class="badge" [class.active]="user.isActive">
{{ user.isActive ? "Active" : "Inactive" }}
</span>
</div>
`,
styles: [
`
.user-card {
border: 1px solid #ddd;
padding: 1rem;
border-radius: 8px;
margin: 1rem 0;
}
.badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.875rem;
}
.badge.active {
background-color: #10b981;
color: white;
}
.badge:not(.active) {
background-color: #ef4444;
color: white;
}
`,
],
})
export class UserCardComponent {
user = {
name: "John Doe",
email: "john@example.com",
isActive: true,
};
}
4. Create a Parent Component to Use It
// app.component.ts
import { Component } from "@angular/core";
import { UserCardComponent } from "./user-card/user-card.component";
@Component({
selector: "app-root",
standalone: true,
imports: [UserCardComponent], // Import standalone component directly!
template: `
<h1>User Management</h1>
<app-user-card></app-user-card>
`,
})
export class AppComponent {}
5. Bootstrap the Application
// main.ts
import { bootstrapApplication } from "@angular/platform-browser";
import { AppComponent } from "./app/app.component";
bootstrapApplication(AppComponent).catch((err) => console.error(err));
Challenge Tasks
Task 1: Add Input Properties
Modify UserCardComponent to accept user data via @Input:
import { Component, Input } from "@angular/core";
interface User {
name: string;
email: string;
isActive: boolean;
}
@Component({
selector: "app-user-card",
standalone: true,
// ...
})
export class UserCardComponent {
@Input({ required: true }) user!: User;
}
// app.component.ts
@Component({
selector: "app-root",
standalone: true,
imports: [UserCardComponent],
template: `
<h1>User Management</h1>
@for (user of users; track user.email) {
<app-user-card [user]="user"></app-user-card>
}
`,
})
export class AppComponent {
users = [
{ name: "John Doe", email: "john@example.com", isActive: true },
{ name: "Jane Smith", email: "jane@example.com", isActive: false },
{ name: "Bob Johnson", email: "bob@example.com", isActive: true },
];
}
Task 2: Add Output Events
Add a click event to toggle user status:
import { Component, Input, Output, EventEmitter } from "@angular/core";
@Component({
selector: "app-user-card",
standalone: true,
imports: [CommonModule],
template: `
<div class="user-card" (click)="toggleStatus()">
<!-- existing template -->
</div>
`,
})
export class UserCardComponent {
@Input({ required: true }) user!: User;
@Output() statusToggled = new EventEmitter<string>();
toggleStatus() {
this.statusToggled.emit(this.user.email);
}
}
Task 3: Create a Standalone Pipe
// capitalize.pipe.ts
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
name: "capitalize",
standalone: true,
})
export class CapitalizePipe implements PipeTransform {
transform(value: string): string {
return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
}
}
Use it in the component:
import { CapitalizePipe } from "./capitalize.pipe";
@Component({
selector: "app-user-card",
standalone: true,
imports: [CommonModule, CapitalizePipe], // Import the pipe
template: ` <h2>{{ user.name | capitalize }}</h2> `,
})
export class UserCardComponent {
// ...
}
Verification
Run the application:
ng serve
You should see:
- Multiple user cards displayed
- Status badges with correct colors
- Click interaction working
- Capitalized names
Key Takeaways
✅ Standalone components eliminate NgModule boilerplate ✅ Direct imports make dependencies explicit ✅ Easier to test and reuse ✅ Better tree-shaking for smaller bundles
Next Steps
Move on to Exercise 2: Functional Guards & Resolvers