Getting Started with Angular 15: Building a Simple Todo App

Angular 15, released in November 2022, introduced powerful features like standalone components, improved image directives, and better tree-shakability. This tutorial guides you through setting up Angular 15 and building a basic Todo application. We’ll cover installation, project creation, components, services, and data binding. By the end, you’ll have a functional app. Prerequisites: Basic knowledge of HTML, CSS, JavaScript, and TypeScript. Node.js (version 14 or later) installed.

Step 1: Install Angular CLI

First, install the Angular CLI globally. Open your terminal and run:

text

npm install -g @angular/cli@15

This installs version 15 specifically. Verify with ng version, which should show Angular CLI: 15.x.x and Angular: 15.x.x.

Step 2: Create a New Project

Generate a new Angular project:

text

ng new todo-app --standalone

The –standalone flag enables standalone components by default, a key Angular 15 feature that reduces boilerplate by eliminating the need for NgModules in many cases. Navigate into the project folder: cd todo-app. Start the development server: ng serve. Open http://localhost:4200 in your browser to see the default app.

Step 3: Understand Project Structure

Angular 15’s structure is streamlined. Key files:

With standalone components, app.module.ts is optional.

Step 4: Create Components

Let’s build our Todo app. Generate a TodoList component:

text

ng generate component todo-list --standalone

This creates todo-list.component.ts with @Component({ standalone: true }). In todo-list.component.ts, add:

typescript

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-todo-list',
  standalone: true,
  imports: [CommonModule, FormsModule],
  templateUrl: './todo-list.component.html',
  styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent {
  todos: string[] = [];
  newTodo: string = '';

  addTodo() {
    if (this.newTodo) {
      this.todos.push(this.newTodo);
      this.newTodo = '';
    }
  }

  removeTodo(index: number) {
    this.todos.splice(index, 1);
  }
}

Here, we import necessary modules directly into the component—another Angular 15 perk.

In todo-list.component.html:

html

<h2>Todo List</h2>
<input [(ngModel)]="newTodo" placeholder="Add new todo">
<button (click)="addTodo()">Add</button>
<ul>
  <li *ngFor="let todo of todos; let i = index">
    {{ todo }} <button (click)="removeTodo(i)">Remove</button>
  </li>
</ul>

This uses two-way binding with ngModel and structural directives like *ngFor.

Step 5: Integrate into App Component

Update app.component.ts to include TodoListComponent:

typescript

import { Component } from '@angular/core';
import { TodoListComponent } from './todo-list/todo-list.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TodoListComponent],
  template: '<app-todo-list></app-todo-list>'
})
export class AppComponent {}

Remove the default content from app.component.html if needed.

Step 6: Add a Service for Data Persistence

For better architecture, create a service to manage todos:

text

ng generate service todo

In todo.service.ts:

typescript

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class TodoService {
  private todos: string[] = [];

  getTodos(): string[] {
    return this.todos;
  }

  addTodo(todo: string) {
    this.todos.push(todo);
  }

  removeTodo(index: number) {
    this.todos.splice(index, 1);
  }
}

Update todo-list.component.ts to use the service:

typescript

import { Component, inject } from '@angular/core';
import { TodoService } from '../todo.service';
// ... other imports

@Component({
  // ...
})
export class TodoListComponent {
  todoService = inject(TodoService);
  newTodo: string = '';

  get todos() {
    return this.todoService.getTodos();
  }

  addTodo() {
    if (this.newTodo) {
      this.todoService.addTodo(this.newTodo);
      this.newTodo = '';
    }
  }

  removeTodo(index: number) {
    this.todoService.removeTodo(index);
  }
}

Angular 15’s inject() function simplifies dependency injection.

Step 7: Enhance with Routing

Add routing for scalability. Generate a Home component:

text

ng generate component home --standalone

Update app.component.ts to use RouterModule:

typescript

import { Component } from '@angular/core';
import { RouterOutlet, provideRouter } from '@angular/router';
import { bootstrapApplication } from '@angular/platform-browser';
import { routes } from './app.routes'; // Create this file

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {}

bootstrapApplication(AppComponent, {
  providers: [provideRouter(routes)]
});

Create app.routes.ts:

typescript

import { Routes } from '@angular/router';
import { TodoListComponent } from './todo-list/todo-list.component';
import { HomeComponent } from './home/home.component';

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'todos', component: TodoListComponent }
];

In home.component.html, add a link: <a routerLink=”/todos”>Go to Todos</a>.

Step 8: Build and Deploy

Test with ng serve. For production: ng build –prod. The output in dist/ can be deployed to services like Vercel or GitHub Pages.

Angular 15 improves performance with automatic tree-shaking of unused code and the ngOptimizedImage directive for lazy loading images (add it to your app for practice).