Build a Full-Stack Task Manager with Angular and Node.js

Build a Full-Stack Task Manager with Angular and Node.js

Build a Full-Stack Task Manager with Angular and Node.js

Build a complete task manager from scratch — a real-world project that covers the full Angular development workflow.

Features

  • User registration and JWT authentication
  • Create, edit, delete, and complete tasks
  • Drag-and-drop reordering via Angular CDK
  • Filter by status, priority, and due date
  • NgRx SignalStore for state management
  • Angular Material UI components

Step 1 — Project Setup

ng new task-manager --routing --style=scss
cd task-manager
npm install @angular/material @angular/cdk @ngrx/signals
ng add @angular/material

Step 2 — Task Store with NgRx Signals

// stores/task.store.ts
export const TaskStore = signalStore(
  { providedIn: 'root' },
  withState({
    tasks:   [] as Task[],
    loading: false,
    filter:  'all' as 'all' | 'active' | 'completed',
  }),
  withEntities<Task>(),
  withComputed(({ tasks, filter }) => ({
    visibleTasks: computed(() => {
      const f = filter();
      return tasks().filter(t =>
        f === 'all'       ? true :
        f === 'active'    ? !t.completed :
        t.completed
      );
    }),
    completedCount: computed(() => tasks().filter(t => t.completed).length),
  })),
  withMethods((store, taskService = inject(TaskService)) => ({
    loadTasks: rxMethod<void>(
      pipe(
        tap(() => patchState(store, { loading: true })),
        switchMap(() => taskService.getAll().pipe(
          tapResponse({
            next:  tasks => patchState(store, setAllEntities(tasks), { loading: false }),
            error: ()    => patchState(store, { loading: false }),
          })
        ))
      )
    ),
    toggleTask(id: number): void {
      patchState(store, updateEntity({ id, changes: t => ({ completed: !t.completed }) }));
      taskService.toggle(id).subscribe();
    },
  }))
);

Step 3 — Task List with CDK Drag-Drop

<!-- task-list.component.html -->
<ul cdkDropList (cdkDropListDropped)="onReorder($event)">
  @for (task of store.visibleTasks(); track task.id) {
    <li cdkDrag [class.done]="task.completed">
      <mat-checkbox [checked]="task.completed"
                    (change)="store.toggleTask(task.id)">
        {{ task.title }}
      </mat-checkbox>
      <span class="priority {{ task.priority }}">{{ task.priority }}</span>
      <button mat-icon-button (click)="store.deleteTask(task.id)">
        <mat-icon>delete</mat-icon>
      </button>
    </li>
  }
</ul>

Step 4 — Node.js API

// server/routes/tasks.js
router.get('/:userId/tasks', authMiddleware, async (req, res) => {
  const tasks = await Task.find({ userId: req.params.userId }).sort({ order: 1 });
  res.json(tasks);
});

router.post('/:userId/tasks', authMiddleware, async (req, res) => {
  const task = await Task.create({ ...req.body, userId: req.params.userId });
  res.status(201).json(task);
});
All Comments