Build A ToDo List Application Using Angular, .Net 5.0 Web API And Microsoft SQL Server

Introduction

This article shows how to build an Angular front end of a ToDo list application. This is part 2 of a two-part tutorial. Part 1 showed how to build a ToDo list application using .Net 5 Web API, JWT authentication, and AspNetCore Identity. Part 1  can be found here. This article starts by stating user stories showing how a user wants to use the application.

The article will show all the necessary steps that are required to build a complete ToDo application frontend. In the end, the article demonstrates that only logged-in users can access the ToDo list endpoints.

Table of Contents

  • Tools
  • User Stories
  • Create a new Angular application
  • Create Angular components
  • Create models
  • Create services
  • Update model files
    • todo-item.model.ts
    • login.model.ts
    • register.model.ts
  • Update service files
    • todo-app.service.ts 
    • auth-guard.service.ts
  • Update the app.module.ts file
  • Update component files
  • Add Bootstrap
  • Allow CORS
  • Conclusion

Tools

  • Visual Studio Code

User Stories

As a user, I want to register to use the to-do application
The user should be able to register her/his credentials to be able to use the to-do app.
As a user, I want to login to use the to-do application
The user should be able to login to the to-do application using her/his credentials.
As a user, I want to create a new to-do item
The user should be able to create a new to-do item, the item should have a name and a description.
As a user, I want to edit an existing item
The user should be able to edit an existing to-do item, the user should be able to edit the name and description.
As a user, I want to view all my to-do items
The user should be able to view all his/her to-do items.
As a user, I want to update the status of a to-do item
As a user, I want to mark a to-do item as done.


Create a new Angular application

Create a new Angular application by opening the command prompt and navigating to the folder where you want to create the application. To create a new application, type the following command and press enter:

ng new ToDoApp 

Select No for Angular routing and choose CSS for styling.

Navigate to the newly created ToDoApp and open the application using Visual Studio Code. The project template will have this structure.

Create Angular components

Create the application Angular components. The application will have four components and one child component. The todo-items component, todo-item-form child component, home component, login component, and register component. Create only the Typescript files and the HTML files by using the below commands:

ng g c todo-items -s --skipTests
ng g c todo-items/todo-item-form -s --skipTests
ng g c home -s --skipTests
ng g c login -s --skipTests
ng g c register -s --skipTests

The folder structure of the project now looks as shown below. The above command will also update the app.module.ts file accordingly.

Create models

Create a folder called models that will contain a login model, register model, and todo-item model. Create only the Typescript files by using the below commands:

ng g class models/todo-item --type=model --skipTests
ng g class models/login --type=model --skipTests
ng g class models/register --type=model --skipTests

The folder structure of the project now looks as shown below.

Create services

Create a folder called services models that will contain auth-guard services files and todo-app services. Create only the Typescript files by using the below commands:

ng g s services/auth-guard --skip-tests
ng g s services/todo-app --skip-tests

The folder structure of the project now looks as shown below.

Update model files

The model fields must match the models that were created for the .NET Web API backend.

todo-item.model.ts

export class TodoItem {
    itemId: number=0;
    itemName: string="";
    itemDescription: string="";
    itemStatus: boolean=false;
}

login.model.ts

export class Login {
    username: string="";
    password: string="";
}

register.model.ts

export class Register {
    username: string="";
    email: string="";
    password: string="";
}

Update service files

todo-app.service.ts

This service will communicate with the backend to send http requests and receive the http response.

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { TodoItem } from '../models/todo-item.model';
import { Login } from '../models/login.model';
import { Register } from '../models/register.model';

@Injectable({
  providedIn: 'root'
})
export class TodoAppService {
  readonly baseURL = "http://localhost:24288/api/ToDoItem";
  readonly authURL = "http://localhost:24288/api/Authentication";
  list: TodoItem[]=[];
  
  constructor(private http: HttpClient) { }

  todoData: TodoItem = new TodoItem();
  loginData: Login = new Login();
  registerData: Register = new Register();

  postToDoItem() {
    return this.http.post(this.baseURL, this.todoData, {
      headers: new HttpHeaders({
        "Content-Type": "application/json"
      })
    });
  }

  putToDoItem() {
    console.log(this.todoData.itemId);
    console.log("Hello");
    console.log(this.todoData);
    return this.http.put(`${this.baseURL}/${this.todoData.itemId}`, this.todoData, {
      headers: new HttpHeaders({
        "Content-Type": "application/json"
      })
    });
  }

  deleteToDoItem(id: number) {
    return this.http.delete(`${this.baseURL}/${id}`, {
      headers: new HttpHeaders({
        "Content-Type": "application/json"
      })
    });
  }

  refreshList() {
    this.http.get(this.baseURL)
    .toPromise()
    .then(res => {
      this.list = res as TodoItem[]
    });
  }

  loginUser() {
    return this.http.post(`${this.authURL}/login`, this.loginData, {
      headers: new HttpHeaders({
        "Content-Type": "application/json"
      })
    });
  }

  registerUser() {
    return this.http.post(`${this.authURL}/register`, this.registerData, {
      headers: new HttpHeaders({
        "Content-Type": "application/json"
      })
    });
  }

}

auth-guard.service.ts 

Install the angular2-jwt library by running the following command:

npm install @auth0/angular-jwt

Update the auth-guard.service.ts file.

This service protects the Angular routes. It implements the canActivate method to act as a guard to Angular routes.

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { JwtHelperService } from '@auth0/angular-jwt';

@Injectable({
  providedIn: 'root'
})
export class AuthGuardService implements CanActivate {

  constructor(private jwtHelper: JwtHelperService,
    private router: Router) { }

    canActivate() {
      const token = localStorage.getItem("jwt");
  
      if (token && !this.jwtHelper.isTokenExpired(token)) {
        return true;
      }
  
      this.router.navigate(["login"]);
      return false;
    }
}

Update the app.module.ts file

Install the ngx-toastr library by running the following command:

npm i ngx-toastr

Update the angular.json file. Make sure you update the styles section under the build section with the following code

"styles": [
              "src/styles.css",
              "node_modules/ngx-toastr/toastr.css"
            ],

Update the app.module.ts file.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { ToastrModule } from 'ngx-toastr';

import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { JwtModule } from '@auth0/angular-jwt';
import { AppComponent } from './app.component';
import { TodoItemsComponent } from './todo-items/todo-items.component';
import { TodoItemFormComponent } from './todo-items/todo-item-form/todo-item-form.component';
import { HomeComponent } from './home/home.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
import { AuthGuardService } from './services/auth-guard.service';


export function tokenGetter() {
  return localStorage.getItem("jwt");
}

@NgModule({
  declarations: [
    AppComponent,
    TodoItemsComponent,
    TodoItemFormComponent,
    HomeComponent,
    LoginComponent,
    RegisterComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    BrowserAnimationsModule,
    ToastrModule.forRoot(),
    RouterModule.forRoot([
      { path: '', component: HomeComponent },
      { path: 'register', component: RegisterComponent },
      { path: 'login', component: LoginComponent },
      { path: 'dashboard', component: TodoItemsComponent, canActivate: [AuthGuardService],
        children: [
          { path: 'payment', component: TodoItemFormComponent, canActivate: [AuthGuardService] }
        ] },
    ]),
    JwtModule.forRoot({
      config: {
        tokenGetter: tokenGetter,
        allowedDomains: ["localhost:24288"],
        disallowedRoutes: []
      }
    })
  ],
  providers: [AuthGuardService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Update component files

app.component.html 

<div class="container">
  <router-outlet></router-outlet>
</div>

home.component.ts

This file contains a isUserAuthenticated() method that checks if a user is authenticated by checking if they have a valid JSON Web Token. It also has a logOut() method.

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { JwtHelperService } from '@auth0/angular-jwt';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styles: [
  ]
})
export class HomeComponent implements OnInit {

  constructor(private jwtHelper: JwtHelperService,
    private router: Router) { }

  ngOnInit(): void {
  }

  isUserAuthenticated() {
    const token: any = localStorage.getItem("jwt");

    if (token && !this.jwtHelper.isTokenExpired(token)) {
      return true;
    } else {
      return false;
    }
  }

  logOut() {
    localStorage.removeItem("jwt");
  }

}

home.component.html

If a user is authenticated she/he has access to add a to-do item, update a to-do item and delete a to-do item. If they have not authenticated they can either login to the application or register to the application.

<h1>Home Page</h1>

<br>
<div *ngIf="isUserAuthenticated()" style="color:blue;">
  <h2>
    <app-todo-items></app-todo-items>
  </h2>
</div>

<br>
<ul>
  <!-- <li>
      <div class="form-group">
        <button class="btn btn-info btn-lg btn-block" type="submit"><a [routerLink]="['/dashboard']" routerLinkActive="active">ToDo Item</a></button>
    </div>
  </li> -->
  <li *ngIf="!isUserAuthenticated()">
    <div class="form-group">
      <a [routerLink]="['/login']" routerLinkActive="active"><button class="btn btn-info btn-lg btn-block" type="submit">Login</button></a>
    </div>
  </li>
  <li *ngIf="!isUserAuthenticated()">
    <div class="form-group">
      <a [routerLink]="['/register']" routerLinkActive="active"><button class="btn btn-info btn-lg btn-block" type="submit">Register</button></a>
    </div>
  </li>
  <li *ngIf="isUserAuthenticated()">
      <div class="form-group">
        <button class="btn btn-info btn-lg btn-block" type="submit"><a class="logout" (click)="logOut()">Logout</a></button>
    </div>
  </li>
</ul>

login.component.ts

This file contains a login method that communicates with the service to login to a user and returns a JWT and saves it in the local storage.

import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { TodoAppService } from '../services/todo-app.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styles: [
  ]
})
export class LoginComponent implements OnInit {
  invalidLogin: boolean | undefined;

  constructor(public service: TodoAppService,
    private toastr: ToastrService, private router: Router) { }

  ngOnInit(): void {
  }

  login(form: NgForm) {
    this.service.loginUser().subscribe(
      res => {
        const token = (<any>res).token;
        localStorage.setItem("jwt", token);
        this.invalidLogin = false;
        this.router.navigate(["/"]);
        this.toastr.success("LoggedIn successfully", "Payment Detail Register");
      },
      err => {
        this.invalidLogin = true;
      }
    );
  }
}

login.component.html

This file has a form to login to a user. It implements input validation.

<form novalidate #loginForm="ngForm" (submit)="login(loginForm)" autocomplete="off">
    <div class="form-group">
        <label>USERNAME</label>
        <input
            class="form-control form-control-lg"
            placeholder="Username"
            name="username"
            #username="ngModel"
            [(ngModel)]="service.loginData.username"
            required
            [class.invalid]="username.invalid && username.touched"
        >
        <div *ngIf="username.touched">
            <p *ngIf="username.errors?.required">Username is required!</p>
        </div>
    </div>
    <div class="form-group">
        <label>PASSWORD</label>
        <input
            type="password"
            class="form-control form-control-lg"
            placeholder="Password"
            name="password"
            #password="ngModel"
            [(ngModel)]="service.loginData.password"
            required
            pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
            [class.invalid]="password.invalid && password.touched"
        >
        <div *ngIf="password.touched">
            <p *ngIf="password.errors?.required">Password is required!</p>
            <p *ngIf="password.errors?.pattern">
                Minimum eight characters, at least one uppercase letter, one lowercase letter, one number and one special character
            </p>
        </div>
    </div>
    <div class="form-group">
        <button class="btn btn-info btn-lg btn-block" type="submit" [disabled]="loginForm.invalid">SUBMIT</button>
    </div>
    <div class="form-group">
        <a [routerLink]="['/register']" routerLinkActive="active"><button class="btn btn-info btn-lg btn-block" type="submit">Register</button></a>
    </div>
    <div class="form-group">
        <a [routerLink]="['']" routerLinkActive="active"><button class="btn btn-info btn-lg btn-block" type="submit">Home</button></a>
    </div>
</form>

register.component.ts

This file contains a register method that communicates with the service to register a user and store their credentials in the database.

import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { Register } from '../models/register.model';
import { TodoAppService } from '../services/todo-app.service';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styles: [
  ]
})
export class RegisterComponent implements OnInit {

  constructor(public service: TodoAppService,
    private toastr: ToastrService, private router: Router) { }

  ngOnInit(): void {
  }

  register(form: NgForm) {
    this.service.registerUser().subscribe(
      res => {
        this.resetForm(form);
        this.service.refreshList();
        this.toastr.success("Submitted successfully", "User Register");
      },
      err => { console.log(err); }
    );
  }

  resetForm(form: NgForm) {
    form.form.reset();
    this.service.registerData = new Register();
  }

}

register.component.html

This file has a form to register a user. It implements input validation.

<form novalidate #registerForm="ngForm" (submit)="register(registerForm)" autocomplete="off">
    <div class="form-group">
        <label>USERNAME</label>
        <input
            class="form-control form-control-lg"
            placeholder="Username"
            name="username"
            #username="ngModel"
            [(ngModel)]="service.registerData.username"
            required
            [class.invalid]="username.invalid && username.touched"
        >
        <div *ngIf="username.touched">
            <p *ngIf="username.errors?.required">Username is required!</p>
        </div>
    </div>
    <div class="form-group">
        <label>EMAIL</label>
        <input
            class="form-control form-control-lg"
            placeholder="Example@sakhile.com"
            name="email"
            #email="ngModel"
            [(ngModel)]="service.registerData.email"
            required
            pattern="^[\w!#$%&'*+\-/=?\^_`{|}~]+(\.[\w!#$%&'*+\-/=?\^_`{|}~]+)*@((([\-\w]+\.)+[a-zA-Z]{2,4})|(([0-9]{1,3}\.){3}[0-9]{1,3}))$" 
            [class.invalid]="email.invalid && email.touched"
        >
        <div *ngIf="email.touched">
            <p *ngIf="email.errors?.required">Email is required!</p>
            <p *ngIf="email.errors?.pattern">You have entered an invalid email address!!!</p>
        </div>
    </div>
    <div class="form-group">
        <label>PASSWORD</label>
        <input
            type="password"
            class="form-control form-control-lg"
            placeholder="Password"
            name="password"
            #password="ngModel"
            [(ngModel)]="service.registerData.password"
            required
            pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
            [class.invalid]="password.invalid && password.touched"
        >
        <div *ngIf="password.touched">
            <p *ngIf="password.errors?.required">Password is required!</p>
            <p *ngIf="password.errors?.pattern">
                Minimum eight characters, at least one uppercase letter, one lowercase letter, one number and one special character
            </p>
        </div>
    </div>
    <div class="form-group">
        <button class="btn btn-info btn-lg btn-block" type="submit" [disabled]="registerForm.invalid">SUBMIT</button>
    </div>
    <div class="form-group">
        <a [routerLink]="['/login']" routerLinkActive="active"><button class="btn btn-info btn-lg btn-block" type="submit">Login</button></a>
    </div>
    <div class="form-group">
        <a [routerLink]="['']" routerLinkActive="active"><button class="btn btn-info btn-lg btn-block" type="submit">Home</button></a>
    </div>
</form>

todo-item-form.component.ts

This file has a method that creates a new to-do item and a method that updates an existing to-do item. 

import { Component, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { TodoItem } from 'src/app/models/todo-item.model';
import { TodoAppService } from 'src/app/services/todo-app.service';

@Component({
  selector: 'app-todo-item-form',
  templateUrl: './todo-item-form.component.html',
  styles: [
  ]
})
export class TodoItemFormComponent implements OnInit {

  constructor(public service: TodoAppService,
    private toastr: ToastrService) { }

  ngOnInit(): void {
    
  }

  onSubmit(form: NgForm) {
    console.log(this.service.todoData);
    if (this.service.todoData.itemId == 0) {
      console.log("Hello");
      this.insertRecord(form);
    } else {
      this.updateRecord(form);
    }

  }

  insertRecord(form: NgForm) {
    this.service.postToDoItem().subscribe(
      res => {
        this.resetForm(form);
        this.service.refreshList();
        this.toastr.success("Submitted successfully", "ToDo Item Register");
      },
      err => { console.log(err); }
    );
  }



  updateRecord(form: NgForm) {
    this.service.putToDoItem().subscribe(
      res => {
        this.resetForm(form);
        this.service.refreshList();
        this.toastr.info("Updated successfully", "ToDo Item Register");
      },
      err => { console.log(err); }
    );
  }

  resetForm(form: NgForm) {
    form.form.reset();
    this.service.todoData = new TodoItem();
  }

}

todo-item-form.component.html

This file has a form to create a to-do item and to update a to-do item. The item status is implemented in a form of a checkbox, if the checkbox is checked then a to-do task is done.

<form novalidate #form="ngForm" (submit)="onSubmit(form)" autocomplete="off">
    <input
        type="hidden"
        name="itemId"
        [value] = "service.todoData.itemId"
    >
    <div class="form-group">
        <label>ITEM NAME</label>
        <input
            class="form-control form-control-lg"
            placeholder="Item Name"
            name="itemName"
            #itemName="ngModel"
            [(ngModel)]="service.todoData.itemName"
            required
            maxlength="50"
            [class.invalid]="itemName.invalid && itemName.touched"
        >
        <div *ngIf="itemName.touched">
            <p *ngIf="itemName.errors?.required">Item name is required!</p>
        </div>
    </div>
    <div class="form-group">
        <label>ITEM DESCRIPTION</label>
        <input
            class="form-control form-control-lg"
            placeholder="Item Description"
            name="itemDescription"
            #itemDescription="ngModel"
            [(ngModel)]="service.todoData.itemDescription"
            required
            maxlength="150"
            [class.invalid]="itemDescription.invalid && itemDescription.touched"
        >
        <div *ngIf="itemDescription.touched">
            <p *ngIf="itemDescription.errors?.required">Item description is required!</p>
        </div>
    </div>
    <div class="form-group">
        <label>ITEM STATUS</label>
        <input
            class="form-control form-control-lg"
            type="checkbox"
            name="itemStatus"
            #itemStatus="ngModel"
            [(ngModel)]="service.todoData.itemStatus"
            [ngModelOptions]="{standalone: true}"
            required
        >
    </div>
    
    <div class="form-group">
        <button class="btn btn-info btn-lg btn-block" type="submit" [disabled]="form.invalid">SUBMIT</button>
    </div>
</form>

todo-items.component.ts

This file has a method populateForm() that displays all the to-do items and a method to delete a to-do item.

import { Component, OnInit } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { TodoItem } from '../models/todo-item.model';
import { TodoAppService } from '../services/todo-app.service';

@Component({
  selector: 'app-todo-items',
  templateUrl: './todo-items.component.html',
  styles: [
  ]
})
export class TodoItemsComponent implements OnInit {

  constructor(public service: TodoAppService,
    private toastr: ToastrService) { }

  ngOnInit(): void {
    this.service.refreshList();
  }

  populateForm(selectedRecord: TodoItem) {
    this.service.todoData = Object.assign({}, selectedRecord);
  }

  onDelete(id: number) {
    if (confirm("Are you sure you want to delete this record?")) {
      this.service.deleteToDoItem(id)
      .subscribe( res => {
        this.service.refreshList();
        this.toastr.error("Deleted susseccfully", "ToDo Item Register");
      },
      err => {
        console.log(err);
      })
    }
  }

}

todo-items.component.html

This file displays the form to create a to-do item or to update a to-do item and a table that displays all the to-do items.

<div class="jumbotron py-3">
    <h1 class="display-4 text-center">ToDo Item Register</h1>
</div>

<div class="row">
    <div class="col-md-6">
        <app-todo-item-form></app-todo-item-form>
    </div>
    <div class="col-md-6">
        <table class="table">
            <thead class="table-light">
                <tr>
                    <th>Item Name</th>
                    <th>Item Description</th>
                    <th>Item Status</th>
                    <th>Delete</th>
                </tr>
            </thead>
            <tbody>
                <tr *ngFor="let pd of service.list">
                    <td (click)="populateForm(pd)">{{ pd.itemName }}</td>
                    <td (click)="populateForm(pd)">{{ pd.itemDescription }}</td>
                    <td (click)="populateForm(pd)">{{ pd.itemStatus }}</td>
                    <td>
                        <i class="fa fa-trash-o fa-lg text-danger" (click)="onDelete(pd.itemId)"></i>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
</div>


Add Bootstrap

This application uses Bootstrap and font-awesome for styling. Add Bootstrap and font-awesome links on the index.html file found at the root of the folder structure.

Allow CORS

Update the Startup.cs file, to allow CORS, of the ToDoAPI application that we did in part 1 of the tutorial.

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ToDoAPI.Authentication;
using ToDoAPI.Models;

namespace ToDoAPI
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "ToDoAPI", Version = "v1" });
            });

            services.AddDbContext<ApplicationDbContext>(options =>
           options.UseSqlServer(Configuration.GetConnectionString("SQLConnection")));

            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.SaveToken = true;
                options.RequireHttpsMetadata = false;
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidAudience = Configuration["JWT:ValidAudience"],
                    ValidIssuer = Configuration["JWT:ValidIssuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Secret"]))
                };
            });

            services.AddCors();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseCors(options =>
            options.WithOrigins("http://localhost:4200")
            .AllowAnyMethod()
            .AllowAnyHeader());
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ToDoAPI v1"));
            }

            app.UseAuthentication();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

If you try to access the to-do item before login in into the application, you get redirected to the login page. Once a user has login she/he is redirected to the ToDo page where she/he can add a to-do item, update a to-do item and delete a to-do item. A user can update a to-do item by clicking on the item.

Conclusion

In this article, I showed how to build a ToDo list application frontend using Angular. This is a continuation of the last tutorial, which can be found here. You can find the source code in my GitHub repository.