#angular #capstone #material #production

Day 30 β€” Capstone Project Part 2 features, Polish & Final Deployment

πŸ“˜ Day 30 β€” Capstone Project Part 2: Features, Polish & The Grand Finale

Zero to Hero β€” Hands-on Angular Tutorial

CONGRATULATIONS! You have reached the final day. πŸ† Today we finish our Kanban Board and you will graduate as an Angular Developer.

Today you will:

  • βœ”οΈ Implement β€œAdd Task” using Angular Material Dialogs.
  • βœ”οΈ Add Real-time Filtering (Search Bar).
  • βœ”οΈ Implement Delete Task with confirmation.
  • βœ”οΈ Perform the Final Production Build.

🟦 1. The β€œAdd Task” Dialog

Instead of a boring prompt(), let’s use a professional Modal.

Generate Dialog Component:

ng g c components/task-dialog

task-dialog.component.ts:

import { Component, inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { FormsModule } from '@angular/forms';

@Component({
  standalone: true,
  imports: [MatDialogModule, MatFormFieldModule, MatInputModule, MatButtonModule, FormsModule],
  template: `
    <h2 mat-dialog-title>New Task</h2>
    <mat-dialog-content>
      <mat-form-field>
        <mat-label>Title</mat-label>
        <input matInput [(ngModel)]="data.title">
      </mat-form-field>
    </mat-dialog-content>
    <mat-dialog-actions>
      <button mat-button (click)="cancel()">Cancel</button>
      <button mat-raised-button color="primary" [mat-dialog-close]="data.title">Create</button>
    </mat-dialog-actions>
  `
})
export class TaskDialogComponent {
  readonly dialogRef = inject(MatDialogRef<TaskDialogComponent>);
  data = { title: '' }; // Model for the form

  cancel() {
    this.dialogRef.close();
  }
}

Update board.component.ts to open it:

import { MatDialog } from '@angular/material/dialog';
import { TaskDialogComponent } from '../task-dialog/task-dialog.component';

export class BoardComponent {
  readonly dialog = inject(MatDialog);

  addTask(columnId: string) {
    const dialogRef = this.dialog.open(TaskDialogComponent, {
      width: '300px'
    });

    dialogRef.afterClosed().subscribe(result => {
      if (result) {
        this.store.addTask(columnId, result); // Call the store!
      }
    });
  }
}

🟩 2. Real-time Search (Computed Signals)

Let’s add a search bar that instantly filters tasks across all columns.

Update board.store.ts:

// 1. Add 'filter' to state
type BoardState = {
  board: Board;
  filter: string; // New
};

// ... initial state ... filter: ''

export const BoardStore = signalStore(
  // ...
  withState(initialState),
  withComputed(({ board, filter }) => ({
    
    // 2. Create a computed view of the board
    filteredBoard: computed(() => {
      const searchTerm = filter().toLowerCase();
      
      return {
        ...board(),
        columns: board().columns.map(col => ({
          ...col,
          // Only show tasks that match the title
          tasks: col.tasks.filter(t => t.title.toLowerCase().includes(searchTerm))
        }))
      };
    })

  })),
  withMethods((store) => ({
    updateFilter(query: string) {
      patchState(store, { filter: query });
    }
  }))
);

Update board.component.html: Change the loop to use store.filteredBoard() instead of store.board()!

<input 
  type="text" 
  placeholder="Search tasks..." 
  (input)="store.updateFilter($event.target.value)">

<!-- Use the filtered version -->
<div *ngFor="let col of store.filteredBoard().columns">
   ...
</div>

🟧 3. Deleting Tasks

Add a small β€œX” button to the task card.

board.component.html:

<div class="task-card" cdkDrag>
  {{ task.title }}
  <button cls="delete-btn" (click)="delete(col.id, task.id)">πŸ—‘οΈ</button>
</div>

board.store.ts:

deleteTask(columnId: string, taskId: string) {
  patchState(store, (state) => ({
    board: {
      ...state.board,
      columns: state.board.columns.map(col => 
        col.id === columnId 
          ? { ...col, tasks: col.tasks.filter(t => t.id !== taskId) }
          : col
      )
    }
  }));
}

πŸŸ₯ 4. The Final Build

Your app is complete. It has state, UI, routes, interactions, and styles. Let’s package it for the world.

# 1. Audit for performance
ng build --stats-json

# 2. Build for Production
ng build --configuration production

Artifacts (dist/):

  • index.html
  • main.[hash].js (Minified, Tree-shaken)
  • styles.[hash].css

Upload this folder to Netlify, Vercel, or Firebase Hosting (See Day 19).


πŸŽ“ Graduation: The Journey

You started 30 days ago knowing the basics (or nothing). Look at what you know now:

  1. Core: Components, Directives, Pipes.
  2. Architecture: Services, Dependency Injection, Signals.
  3. Data: HttpClient, RxJS, Interceptors.
  4. State: NgRx SignalStore.
  5. Quality: Unit Testing, E2E, ESLint, Husky.
  6. Advanced: SSR, PWA, Micro-Frontends, Schematics.

Where to go from here?

  • Build: Stop reading. Start building. Clone Twitter. Clone AirBnb.
  • Contribute: Look at Open Source Angular projects.
  • Community: Join the Angular Discord or follow the Angular Blog.

You are now an Angular Developer. Go build something amazing. πŸš€

β€” The End β€”