FrontendAtlas
Interview Questions
Home>Angular interview questions> Angular coding challenges >Todo List (Standalone Component with @for)

Todo List (Standalone Component with @for)

easy
By FrontendAtlas Editorial · Updated Jul 15, 2026
easy level
Standalone component setup with `standalone: true` Managing in-memory state in a component Rendering lists with `@for` and an explicit `track` expression Using `[(ngModel)]` for simple form input binding Basic UX for add / toggle / remove actions
Build a small todo list using an Angular standalone component. Add tasks, mark them complete, remove them, and render stable list identity with modern @for (...; track ...) control flow.
Standalone component setup with standalone: trueManaging in-memory state in a componentRendering lists with @for and an explicit track expressionUsing [(ngModel)] for simple form input bindingBasic UX for add / toggle / remove actions

Requirements

  • Use a standalone Angular component as the root component.
  • Display a text input and an "Add" button for creating new todos.
  • Render todo items with @for (todo of todos; track todo.id).
  • Each todo item should show a label and a checkbox to toggle completion.
  • Allow removing a single todo item from the list.
  • Optionally provide a "Clear completed" action to remove all completed todos.

Expected behavior

  • Typing a non-empty value and clicking "Add" appends a new todo to the list and clears the input.
  • Empty or whitespace-only values are ignored (no item is added).
  • Clicking a todo’s checkbox toggles its completed state.
  • Completed todos are visually distinguished (e.g. line-through).
  • Clicking a remove button deletes that specific todo.
  • If you implement "Clear completed", it removes only the completed todos.

Implementation notes

  • Keep everything in AppComponent (src/app/app.component.ts and app.component.html). No extra services or components are required.
  • Use a simple array of objects (e.g. { id, text, done }) to store todos.
  • Use @for (todo of todos; track todo.id) so item identity remains stable without an extra component tracking helper.
  • Use [(ngModel)] for the input binding and checkbox bindings (remember to import FormsModule).
  • Use click handlers like (click)="addTask()" and (click)="removeTask(task)" to modify the array.

Solution

Overview

This exercise models a classic todo list with a single standalone component. You keep all state inside the component: a newTask string for the input and a todos array (e.g. { id, text, done }). The template binds newTask with [(ngModel)], renders todos with @for (todo of todos; track todo.id), and wires up click handlers to add, toggle, and remove items.

Typical flow:

  • User types into the input; newTask is updated via [(ngModel)].
  • Clicking **Add** (or pressing Enter, if you implement it) calls addTask(), which trims the text, ignores empties, pushes a new todo into the array, and resets newTask.
  • Each tracked todo row shows a checkbox bound to todo.done and maybe styled with a .done CSS class when complete.
  • Clicking the remove button calls removeTask(todo), which filters that item out.
  • Optionally, a "Clear completed" button calls clearCompleted() to remove all completed todos.

**Test cases**

  • With an empty input, clicking **Add** does nothing.
  • Typing "Buy milk" and clicking **Add** shows a single todo row with that text.
  • Toggling the checkbox marks the item as completed and applies the completed styling.
  • Clicking the remove button deletes that specific row.
  • After completing multiple todos, clicking "Clear completed" (if implemented) removes only the completed ones.
1

Standalone component with a local array of todos

Represent todos as { id, text, done } objects stored in a todos array property. Use a simple incremental nextId counter for unique ids. The input is bound via [(ngModel)]="newTask". In addTask(), trim newTask, ignore empties, then push a new object. In the template, use @for (todo of todos; track todo.id) to render each row with stable identity and no separate component tracking method. Each row contains a checkbox bound to todo.done and a remove button.

toggleTask(todo) can simply flip todo.done = !todo.done, or you can rely entirely on [(ngModel)] on the checkbox and only use handlers for extra behavior. removeTask(todo) filters todos to drop the item by id. clearCompleted() sets todos to todos.filter(t => !t.done). This keeps all list and input logic colocated in the root standalone component.

Files

{
  "name": "angular-todo-list-solution",
  "private": true
}
{
  "compilerOptions": {
    "target": "es2022",
    "strict": true,
    "noEmitOnError": false
  }
}
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Angular SDK • Todo List (solution)</title>
  </head>
  <body>
    <app-root></app-root>
  </body>
</html>
import 'zone.js';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent).catch(console.error);
:root {
  color-scheme: dark;
}

html, body {
  margin: 0;
  height: 100%;
  background: #0a0a0a;
}

body {
  font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
}
.panel {
  background: #0b1020;
  border: 1px solid #1f2937;
  border-radius: 12px;
  padding: 16px;
  display: grid;
  gap: 12px;
}

.input-row {
  display: flex;
  gap: 8px;
}

.input-row input {
  flex: 1;
  background: #020617;
  border-radius: 8px;
  border: 1px solid #1f2937;
  padding: 8px 10px;
  color: #e5e7eb;
  font-size: 14px;
}

.input-row input:focus {
  outline: none;
  border-color: #6366f1;
  box-shadow: 0 0 0 1px #6366f1;
}

.primary {
  background: #4ade80;
  color: #022c22;
  border: none;
  border-radius: 999px;
  padding: 8px 14px;
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
}

.primary[disabled] {
  opacity: .6;
  cursor: not-allowed;
}

.list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 6px;
}

.item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 8px;
  background: #020617;
  border: 1px solid transparent;
}

.item-main {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
}

.item-main input[type=checkbox] {
  accent-color: #22c55e;
}

.item-main span.done {
  text-decoration: line-through;
  color: #9ca3af;
}

button.ghost {
  background: transparent;
  color: #e5e7eb;
  border: 1px solid #374151;
  border-radius: 999px;
  padding: 6px 10px;
  font-size: 12px;
  cursor: pointer;
}

button.ghost.small {
  padding: 4px 10px;
  font-size: 12px;
}

.empty {
  margin: 4px 0 0;
  font-size: 13px;
  color: #9ca3af;
  text-align: center;
}

.footer {
  display: flex;
  justify-content: flex-end;
  margin-top: 4px;
}
<main style="min-height:100vh; background:#0a0a0a; color:#e5e7eb; display:grid; place-items:center; padding:24px;">
  <section style="width:100%; max-width:480px; display:grid; gap:16px;">
    <h1 style="margin:0; font-size:20px; font-weight:700;">Todo List</h1>

    <div class="panel">
      <div class="input-row">
        <input type="text" placeholder="Add a new task..." [(ngModel)]="newTask" />
        <button type="button" class="primary" (click)="addTask()">Add</button>
      </div>

      @if (hasTodos) {
        <ul class="list">
        @for (todo of todos; track todo.id) {
          <li class="item">
            <label class="item-main">
              <input type="checkbox" [(ngModel)]="todo.done" />
              <span [class.done]="todo.done">{{ todo.text }}</span>
            </label>
            <button type="button" class="ghost" (click)="removeTask(todo)">Remove</button>
          </li>
        }
        </ul>
      } @else {
        <p class="empty">No tasks yet. Add your first one above.</p>
      }

      @if (hasTodos) {
        <div class="footer">
          <button type="button" class="ghost small" (click)="clearCompleted()">Clear completed</button>
        </div>
      }
    </div>
  </section>
</main>
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

interface Todo {
  id: number;
  text: string;
  done: boolean;
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [FormsModule],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  newTask = '';
  todos: Todo[] = [];
  private nextId = 1;

  get hasTodos(): boolean {
    return this.todos.length > 0;
  }

  addTask(): void {
    const text = this.newTask.trim();
    if (!text) {
      return;
    }

    this.todos = [
      ...this.todos,
      {
        id: this.nextId++,
        text,
        done: false
      }
    ];

    this.newTask = '';
  }

  removeTask(todo: Todo): void {
    this.todos = this.todos.filter(t => t.id !== todo.id);
  }

  clearCompleted(): void {
    this.todos = this.todos.filter(t => !t.done);
  }
}

Notes & Pitfalls

Pitfalls
  • Allowing empty or whitespace-only todos to be added.
  • Mutating the `todos` array in a way that loses track of identity (e.g. reusing the same id for all items).
  • Forgetting to import `FormsModule`, which breaks `[(ngModel)]` bindings.
  • Handling toggle logic only in a click handler but not binding the checkbox correctly.
Techniques
  • Modeling UI items as simple objects inside a component.
  • Using `[(ngModel)]` for a controlled input and checkbox bindings.
  • Rendering lists with `@for` and `track todo.id` for stable identity.
  • Using pure functions (`filter`, `map`) to derive new arrays when removing or clearing items.

Similar questions

Angular Filterable / Searchable User ListintermediateAngular Transfer List (Select + Move Between Two Lists)intermediateMulti-step Signup Form (Reactive Forms)intermediate

Guides

Frontend interview preparation guideGuideFrontend coding interview questions and prep guideBlueprintJavaScript Problems That Actually Show UpBlueprintBuild Great UI in 60 MinutesBlueprint

Preparing for interviews? Use Frontend Coding Challenges first, then move into a concrete Study Plan before targeted Company Prep.

Open frontend interview questionsBrowse Angular interview questionsOpen Essential 60Open Machine Coding HubOpen Frontend Coding ChallengesOpen System DesignOpen Interview Prep GuideOpen System Design BlueprintOpen Framework Prep PathsOpen Study PlansOpen 30-day foundations study planOpen Company PrepOpen Angular Framework Prep Guide
↗Incidents hub
16 / 26