This repository now doubles as a practice workbook.
Use the exercises below to intentionally break/fix features so you learn Angular by doing.
For every topic you will see:
- Goal – what you are learning.
- Exercise – what to implement or fix.
- Common mistakes – what usually goes wrong.
- Solution – a working example.
Tip: Try the exercise first without reading the solution. Then compare.
Run the app and understand where code lives.
- Install dependencies.
- Start the dev server.
- Identify where template and logic are in the main app component.
- Running commands outside project folder.
- Editing generated Angular files without understanding purpose.
npm install
npm run startMain files:
src/main.ts→ bootstraps app.src/app/app.ts→ component class (logic).src/app/app.html→ component template (UI).
Display values from TypeScript and bind DOM properties.
In app.ts add:
title = 'Angular Fix Lab';logoUrl = 'https://angular.dev/assets/images/press-kit/angular_icon_gradient.gif';
In app.html, render title and image using proper bindings.
- Using
{{ }}inside HTML attributes for DOM properties (src="proxy.php?url=https%3A%2F%2Fgithub.com%2F%7B%7BlogoUrl%7D%7D") in situations where property binding is preferred. - Misspelling class property names.
// app.ts
export class App {
title = 'Angular Fix Lab';
logoUrl = 'https://angular.dev/assets/images/press-kit/angular_icon_gradient.gif';
}<!-- app.html -->
<h1>{{ title }}</h1>
<img [src]="logoUrl" [alt]="title" width="120" />React to user actions and update view state.
Create a counter with + and - buttons.
- Forgetting to initialize
count. - Calling a method in template that does not exist.
// app.ts
export class App {
count = 0;
increment() {
this.count++;
}
decrement() {
this.count--;
}
}<!-- app.html -->
<p>Count: {{ count }}</p>
<button (click)="decrement()">-</button>
<button (click)="increment()">+</button>Show/hide blocks based on state.
Display a message only when count is greater than 5.
- Using assignment (
=) instead of comparison (>,===). - Writing very complex logic directly in template.
@if (count > 5) {
<p>You crossed 5 🎉</p>
}Render lists efficiently.
Render a task list and support add/remove behavior.
- Forgetting
trackexpression (hurts rendering performance). - Mutating array in ways that make debugging hard.
// app.ts
export class App {
tasks = [
{ id: 1, name: 'Learn interpolation' },
{ id: 2, name: 'Practice @for' },
];
removeTask(id: number) {
this.tasks = this.tasks.filter((t) => t.id !== id);
}
}<!-- app.html -->
<ul>
@for (task of tasks; track task.id) {
<li>
{{ task.name }}
<button (click)="removeTask(task.id)">Remove</button>
</li>
}
</ul>Use signal, computed, and effect to manage reactive state.
Refactor counter to signals and create derived text.
- Reading signal without calling it (
countinstead ofcount()). - Mutating signal value directly.
import { Component, computed, effect, signal } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.html',
})
export class App {
count = signal(0);
label = computed(() => (this.count() >= 10 ? 'High' : 'Low'));
constructor() {
effect(() => {
console.log('count changed:', this.count());
});
}
increment() {
this.count.update((v) => v + 1);
}
}<p>Count: {{ count() }}</p>
<p>Level: {{ label() }}</p>
<button (click)="increment()">+</button>Split UI into reusable components and pass data.
Generate task-item component that accepts one task and emits remove event.
- Not importing standalone child component into parent.
- Forgetting to emit output event from child.
This branch contains a step-by-step Angular feature walkthrough so you can learn by editing and running one app.
git checkout feat/angular-step-by-stepnpm install
npm startThen open http://localhost:4200.
-
Component + template basics
Appis a standalone component (src/app/app.ts).- Template lives in
src/app/app.html, styles insrc/app/app.scss.
-
Data binding
- One-way binding with
{{ }}interpolation. - Event binding with
(input). - Property binding with
[value].
- One-way binding with
-
Directives and built-in control flow
@forloops through lessons.@ifshows completion messages.
-
Forms
[(ngModel)]captures numeric progress.- Add a custom lesson with input + button event.
-
Signals + computed state
signal()stores name, lessons, and debug state.computed()derives progress labels and completion state.
-
Pipes
datepipe for readable dates.numberandpercentpipe for progress formatting.jsonpipe for optional debug output.
Generate component:
ng generate component task-itemChild component:
import { Component, input, output } from '@angular/core';
@Component({
selector: 'app-task-item',
template: `
<span>{{ task().name }}</span>
<button (click)="removed.emit(task().id)">Remove</button>
`,
})
export class TaskItemComponent {
task = input.required<{ id: number; name: string }>();
removed = output<number>();
}Parent usage:
@for (task of tasks; track task.id) {
<app-task-item [task]="task" (removed)="removeTask($event)" />
}Fetch API data and render loading/error states.
Call a public API (example: JSONPlaceholder todos) and render first 5 items.
- Forgetting to provide
HttpClientsupport in app config. - Not handling loading or error state.
In app.config.ts:
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(),
],
};In app.ts:
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
type Todo = { id: number; title: string; completed: boolean };
export class App {
private http = inject(HttpClient);
todos: Todo[] = [];
loading = true;
error = '';
ngOnInit() {
this.http
.get<Todo[]>('https://jsonplaceholder.typicode.com/todos?_limit=5')
.subscribe({
next: (data) => {
this.todos = data;
this.loading = false;
},
error: () => {
this.error = 'Failed to load todos';
this.loading = false;
},
});
}
}In app.html:
@if (loading) {
<p>Loading...</p>
} @else if (error) {
<p>{{ error }}</p>
} @else {
<ul>
@for (todo of todos; track todo.id) {
<li>{{ todo.title }} - {{ todo.completed ? 'Done' : 'Pending' }}</li>
}
</ul>
}- Hardcode a value in template → move it to class with interpolation.
- Add a button click handler.
- Render list with
@for+track. - Convert mutable primitive state to
signal. - Extract one part into child component.
- Replace hardcoded list with HTTP response.
If something breaks, debug in this order:
- Template binding names.
- Component imports/providers.
- Data shape from API.
- Console errors.
npm run start
npm run build
npm run test
ng generate component my-componentHappy learning 🚀 npm start # dev server npm run build # production build npm test # unit tests