Skip to content
Pvtw

Let's create a todo application in Angular

Hello fellow coders. Today, we are going to build a todo app in Angular. You can follow along with me.

Prerequisites

To install Angular, we need to install the cli with npm. If you don't have npm on your machine, I recommend installing node with nvm with the following command:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

Then install the latest version of node:

nvm install --lts

Now you should been able to use the node and npm commands.

Oke, back to Angular. Now you can install the Angular CLI tool with npm.

npm install -g @angular/cli

With this command, you gain access to the ng command which can be used to create and manage a angular application. First create a new workspace. A workspace can hold several applications. Run this command to create one:

ng new todo

This will create a workspace with the name todo in the todo directory. You will get the option to select the stylesheet format. I personally like to use SCSS. But you can choice another or none if you prefer. Then you get the question if you want SSR (Server-Side rendering). Choice Y for now. If you have git installed, it will also initialize git. To test our application, go to the directory and run:

ng serve --open

It will open your browser and you get a default page like this:

Angular. Hello, todo. Congratulations! Your app is running.

Api

We are going to use a package to act as an API. The package provide endpoints for a given resource in json format.

npm install json-server

Then add this to the scripts in package.json:

"server": "json-server --watch db.json --port=5000"

Create a db.json file in the root of your project with the following contents:

{
    "todos": []
}

This will make sure the endpoints to todos exists.
You can start the server with this command now:

npm run server

You may also add the db.json to the gitignore file to ignore it from your repository.

The first component

Now it is time to generate our first component. In the component, we will store the list of todo's and add links to create and edit a todo. Generate the component with:

ng generate component components/todos

I like to generate the components in the components directory instead of in the root of the application. In this component, we hold a array of todo's. To do that we first make a private property of todo's. To make typing the todo easier, we can create a interface with the properties of a single todo. We store the interface in a interfaces directory. Create a todo.ts file with the following contents:

export interface Todo {
    id?: number;
    title: string;
    completed: boolean;
}

This is how a basic todo item looks like. Notice the id is nullable, so we can use it for both creating and editing the todo.

Now in the todos component we create the property like this:

public todos: Todo[] = [];

While we are there, we can create a unsubscriber as well with:

private unsubscriber$ = new Subject<void>();

By convension we suffix the property with $. To use this unsubscriber, we will implement two interfaces from angular. The OnInit and OnDestroy interface. In the ngOnInit method we won't do anything for now, we will come back to that later. The ngOnDestroy method will look like this:

public ngOnDestroy(): void {
    this.unsubscriber$.next();
    this.unsubscriber$.complete();
}

What this is going to do is removing the subscription when the component is destroyed.

Service

To get our todo's from the API we get from the json-server package, we will create a service. The service can be generated with:

ng generate service services/todo

Again we store the service in a specific directory instead of the root of the application, to make it clear what each file is. The service is responsable for the basic CRUD actions. This is how the file should look like:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Todo } from '../interfaces/todo';

const httpOptions = {
  headers: new HttpHeaders({
    'Content-Type': 'application/json'
  })
};

@Injectable({
  providedIn: 'root'
})
export class TodoService {
  private apiUrl: string = "http://localhost:5000/todos";

  constructor(
    private readonly http: HttpClient
  ) { }

  public getAll(): Observable<Todo[]> {
    return this.http.get<Todo[]>(this.apiUrl);
  }

  public add(todo: Todo): Observable<Todo> {
    return this.http.post<Todo>(this.apiUrl, todo, httpOptions);
  }

  public update(todo: Todo): Observable<Todo> {
    const url = `${this.apiUrl}/${todo.id}`;
    return this.http.put<Todo>(url, todo, httpOptions);
  }

  public delete(todo: Todo): Observable<Todo> {
    const url = `${this.apiUrl}/${todo.id}`;
    return this.http.delete<Todo>(url, httpOptions);
  }
}

Let me explain what is happening here. In the class we are storing the base url to our api. The port should be the same as the server script in your package.json (the --port flag). Then we are injecting the built-in http client provided by Angular. The methods below are our CRUD operations. There is also a global constant with http options. The important thing is the 'Content-Type': 'application/json' to force the output to be json.

Todo component

Now we can create a todo component, which we use to show a single todo. Create the todo component with:

ng generate component components/todo

And add the following input in the class:

@Input() todo!: Todo;

With the ! we tell the compiler that the value will never be null (because we provide the todo via it's input, it can not be null). You can style the component to your liking, but a simple layout for it:

<div class="d-flex justify-content-between align-items-center p-2 border">
    <h2>{{ todo.title }}</h2>
</div>

Form

To add a todo, we need a form. We can create one or atleast the component for it, with:

ng generate component components/todo-form

Then change the ts file with the following:

import { Component, Output, EventEmitter } from '@angular/core';
import { Todo } from '../../interfaces/task';

@Component({
  selector: 'app-todo-form',
  templateUrl: './todo-form.component.html',
  styleUrls: ['./todo-form.component.css']
})
export class TodoFormComponent {
  @Output() submitted: EventEmitter<Todo> = new EventEmitter();

  public title: string = "";
  public completed: boolean = false;

  public onSubmit(): void {
    const todo: Todo = {
      id: Math.floor(Math.random() * 100000),
      title: this.title,
      completed: this.completed
    };

    this.submitted.emit(todo);

    this.clearForm();
  }

  private clearForm(): void {
    this.title = "";
    this.completed = false;
  }
}

Again, what is happening here. We store the title and the boolean whether the todo is completed as properties of the class. Then we create a submit method that is called when the form is submitted. Also a private method that just clears the input fields. Then use this html for the form:

<form class="border p-4" (ngSubmit)="onSubmit()">
    <h2>Add a new task</h2>
    <div class="form-group mt-4">
        <label for="title" class="form-label">Title</label>
        <input type="text" name="title" id="title" class="form-control" [(ngModel)]="title" />
    </div>
    <div class="form-check mt-4">
        <label for="completed" class="form-check-label">Completed</label>
        <input type="checkbox" name="completed" id="completed" class="form-check-input" [(ngModel)]="completed" />
    </div>
    <input type="submit" name="submit" value="Create" class="btn btn-primary mt-4" />
</form>

We are using the ngSubmit to call the onSubmit function whenever the form is submitted. We also use ngModel to bind the value to the properties.

Back to the todos component

Remember the todos component where the ngOnInit method is empty? We will add something to it now! In the ngOnInit method we get all the todo's from our json database. That is very easy because our service has a method that does exactly that. Add a private method to the todos component and call it from the ngOnInit method. Don't forget to import the TodoService from the constructor. This would be the code:

constructor(
  private readonly todoService: TodoService,
) {}

public ngOnInit(): void {
  this.getTodos();
}

private getTodos(): void {
  this.todoService.getAll()
    .pipe(takeUntil(this.unsubscriber$))
    .subscribe((todos) => {
      this.todos = todos
    });
}

We should also include a method to add a todo whenever the form is submitted.

public addTodo(todo: Todo): void {
  this.todoService.add(todo)
    .pipe(takeUntil(this.unsubscriber$))
    .subscribe((t: Todo) => {
       this.todos.push(t);
    });
}

Then in the template of the todos component, listen for the submitted event and call the addTodo method which accept the special $event variable.

<app-todo-form (submitted)="addTodo($event)"></app-todo-form>

You can try it out now. When you type something in the title field and submit the form, you should see the title added to the list below.

Now we need a way to mark the task as completed. We can do that by double clicking the task by listening to the double click event on the title.

<h2 (dblclick)="onDblclick(todo)">{{ todo.title }}</h2>

Then add the following in the ts file:

@Output() dblclicked: EventEmitter<Todo> = new EventEmitter();

public onDblclick(todo: Todo): void {
  this.dblclicked.emit(todo);
}

Now in the todos component, we can listen to that event and toggle the completed boolean in the todo.

<app-todo-item *ngFor="let todo of todos" [todo]="todo" (dblclicked)="toggleCompleted(todo)"></app-todo-item>

Then create the toggleCompleted method in the ts file:

public toggleCompleted(todo: Todo): void {
  todo.completed = !todo.completed;
  this.todoService.update(todo)
    .pipe(takeUntil(this.unsubscriber$))
    .subscribe();
}

And we are done! That took awhile but now you have a todo application built with Angular. You can view my repository here for reference.