Skip to main content

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: true flag
  • imports array 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