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.htmlmain.[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:
- Core: Components, Directives, Pipes.
- Architecture: Services, Dependency Injection, Signals.
- Data: HttpClient, RxJS, Interceptors.
- State: NgRx SignalStore.
- Quality: Unit Testing, E2E, ESLint, Husky.
- 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 β