Web user context
Angular HTTP REST SPA Typescript Visual Code

How to Call a Web API from an Angular Application

Welcome to today’s post.

I will discuss how to use an Angular UI to call a Web API.

In this post I will focus on a simple scenario that has no security between the Angular UI and .Net Core Web API.

When calling a method within a Web API we require a REST HTTP call (GET, POST, PUT, DELETE) that matches the API method on the server.

If our API controller has a route:

api/[controller]/List

then a call to the API from our client is of the form:

http://[server] /[API service name]/api/Book/List

The following diagram depicts this architecture:

Angular web api

In our .NET Core Web API controller we have a List() API method matching the route as shown:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using BookLoan.Models;
using BookLoan.Data;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using BookLoan.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;

namespace BookLoan.Controllers
{
    public class BookController : Controller
    {
        ApplicationDbContext _db;
        IBookService _bookService;

        public BookController(ApplicationDbContext db, 
          IBookService bookService)
        {
            _db = db;
            _bookService = bookService;
        }

        [HttpGet("api/[controller]/List")]
        public async Task<List<BookViewModel>> List()
        {
          List<BookViewModel> bvm = new List<BookViewModel>();
          var books = await _db.Books.ToListAsync();
          return books;
        }
..
}

To avoid CORS related errors when calling the Web API method, you will need to include the CORS policy in your start up code within the .Net Core Web API. With an Angular UI application, the origin is the URL and port where the application is hosted. In the Angular development environment hosted locally we setup CORS as follows with the header, origins, and credentials:

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
    ..
    services.AddCors(options => options.AddPolicy("CorsPolicy",
      builder =>
      {
        builder.AllowAnyMethod().AllowAnyHeader()
          .WithOrigins("http://localhost:4200")
          .AllowCredentials();
      }));
    ..
  }

}

Adding the CORS middleware is done as follows:

public void Configure(IApplicationBuilder app, 
  IHostingEnvironment env)
{
  app.UseCors("Cors");
  ...
}

In our Angular application, we define an API service class which will call the API method.

We use the method HttpClient within the Angular library @angular/common/http. To make a HTTP GET call to the Web API method:

import { Injectable } from '@angular/core'
import { HttpClient, HttpHeaders } from '@angular/common/http'

@Injectable()
export class ApiService {
    constructor(private http: HttpClient) {}

    getBooks(){
        return this.http          .get('http://localhost/BookLoan.Catalog.API/api/Book/List'); 
    }
}

Our Angular component can call this method by subscribing to the API service method getBooks() and assigning the response which are in Json array format a class variable as shown:  

import { Component } from '@angular/core'
import { ApiService } from '../services/api.service';

@Component({
    selector: 'books',
    templateUrl: './books.component.html'
})
export class BooksComponent {
    books
    
    constructor(private api: ApiService) {}

    ngOnInit() {
        this.api.getBooks().subscribe(res => {
            this.books = res
            console.log("read books from API");
        })
    }
}

Calling a Web API method with parameters is straightforward. This involves using the HttpParams class to create the API method input parameters. 

An example API method with an input parameter from the controller is shown below:

http://localhost/BookLoan.Catalog.API/api/Book/Details/2

To call this API method in angular, the web API service controller class is as shown:

[HttpGet("api/[controller]/Details/{id}")]
public async Task<ActionResult> Details(int id)
{
  if (id == 0)
  {
    return NotFound(new { id });
  }
  
  try
  {
    BookStatusViewModel bvm = new BookStatusViewModel();
    var book = await _db.Books.Where(a => a.ID == id) 
      .SingleOrDefaultAsync();
    if (book != null)
    {
      bvm.ID = book.ID;
      bvm.Title = book.Title;
      bvm.Author = book.Author;
      bvm.Genre = book.Genre;
      bvm.Location = book.Location;
      bvm.YearPublished = book.YearPublished;
      bvm.Edition = book.Edition;
      bvm.Genre = book.Genre;
      bvm.DateCreated = book.DateCreated;
      bvm.DateUpdated = book.DateUpdated;
    }
    return Ok(bvm);
  }
  catch (Exception ex)
  {
    return BadRequest( new { ex.Message });
  }
}

The Angular component method to obtain the response is as shown:

getBook(id)
{
  return this.http.get(
'http://localhost/BookLoan.Catalog.API/api/Book/Details/' + id); 
}

Note that if we tried to use HttpParams in the following call:

var params = new HttpParams();
params = params.set('id', id);
return this.http.get( 'http://localhost/BookLoan.Catalog.API/api/Book/Details/', {params}); 

Our parameters will be defined as shown within the Chrome debugger:

Despite the parameters being clearly defined, we will still get the following 404 error:

The parameters would fail to match the Web API method signature and give us a 404 error.

The URL route for our HTTP GET call containing the single query parameter below would be a problem:

http://localhost/BookLoan.Catalog.API/api/Book/Details?id=2

The use of the HttpParams class would be applicable to multiple query parameters to a URL like so:

http://localhost/BookLoan.Catalog.API/api/Book/Details?id1=2&id2=3

When the following format is used for the single parameter:

http://localhost/BookLoan.Catalog.API/api/Book/Details/2

the response will be valid as shown:

In a previous post I showed how to submit a HTTP POST with a JSON payload to a Web API using POSTMAN. I will show how to apply a POST (creating new data) and PUT (update existing data).

To insert new records to the data store we post data from an Angular form to the Web API using HTTP POST. The Web API backend code is a method call with the model instance of the object to be inserted. This is shown below:

[HttpPost("api/[controller]/Create")]
public async Task<ActionResult> Create([FromBody] 
  BookViewModel model)
{
  try
  {
    string sTitle = model.Title; 
    string sAuthor = model.Author; 
    int iYearPublished = System.Convert.ToInt32(model.YearPublished.ToString());
                
    string sGenre = model.Genre; 
    string sEdition = model.Edition; 
    string sISBN = model.ISBN; 
    string sLocation = model.Location; 

    BookViewModel book = new BookViewModel()
    {
      Title = sTitle,
      Author = sAuthor,
      YearPublished = iYearPublished,
      Genre = sGenre,
      Edition = sEdition,
      ISBN = sISBN,
      Location = sLocation,
      DateCreated = DateTime.Today
    };

    if (ModelState.IsValid)
    {
      await _bookService.SaveBook(book);
      return RedirectToAction("Details", "Book", 
         new { id = book.ID });
    }
    return Ok(book); 
  }
  catch (Exception ex)
  {
    _logger.LogError(ex.Message);
    return BadRequest("Cannot create book record.");
  }
}

I will now show how you update data from an Angular form to the Web API using a HTTP PUT (although updates can also be done using a HTTP POST without an ID parameter).

Below is the Web API update method with the ID parameter and the body of the data to be updated:

[HttpPut("api/[controller]/Edit/{id}")]
public async Task<ActionResult> Edit(int id, [FromBody] BookViewModel model)
{
  try
  {
    int Id = model.ID; 
    string sTitle = model.Title; 
    string sAuthor = model.Author; 
    int iYearPublished = System.Convert.ToInt32(model.YearPublished);
    string sGenre = model.Genre; 
    string sEdition = model.Edition; 
    string sISBN = model.ISBN; 
    string sLocation = model.Location; 

    BookViewModel updatedata = new BookViewModel()
    {
      ID = Id,        
      Title = sTitle,
      Author = sAuthor,
      Edition = sEdition,
      Genre = sGenre,
      ISBN = sISBN,
      Location = sLocation,
      YearPublished = iYearPublished,
      DateUpdated = DateTime.Now
    };

    BookViewModel updated = await 
      _bookService.UpdateBook(id, updatedata);
    if (updated != null)
      return RedirectToAction("Details", "Book", 
        new { id = updated.ID});
    return Ok(true);
  }
  catch (Exception ex)
  {
    return BadRequest(ex.Message);
  }
}

The URL route for the update is of the form:

http://localhost/BookLoan.Catalog.API/api/Book/Edit/[number]

The URL route for the insert is of the form:

http://localhost/BookLoan.Catalog.API/api/Book/Create

To be able to pass in payload data from Angular, we declare a TypeScript class that has identically named members that match the properties of the corresponding BookViewModel class in the Web API. When the payload is passed from the Angular UI using POST or PUT, the client side class members are mapped to class members and casing of member names is ignored.

Below is the book model class TypeScript definition:

export class Book
{
  public id: number;
  public title: string;
  public author: string;
  public yearPublished: number;
  public genre: string;
  public edition: string;
  public isbn: string;
  public location: string;
  public dateCreated: Date;
  public dateUpdated: Date;
}

To update or save (create) a record in the Web API, the following methods are declared in an Angular service API:

import { Book } from '../models/Book';

Insert and update HTTP POST and HTTP PUT calls.

We use the http.post() method for HTTP POST calls:

saveBook(book) 
{
  return this.http.post<Book>  ('http://localhost/BookLoan.Catalog.API/api/Book/Create', book);    
}

We use the http.put() method for HTTP PUT calls:

updateBook(book) 
{
  return this.http.put<Book>('http://localhost/BookLoan.Catalog.API/api/Book/Edit/' + 
book.id, book);
}

When the UI is run, a record is retrieved into the UI form:

When the Update button is clicked the request is posted to the Book Catalog Web API. In the browser debugger the request URL is shown with status code:

When a new record is created, the equivalent call for a HTTP POST is shown below:

The response is shown below the request header. In this case the detail record is returned as a response:

Any authentication headers that have been appended to the HTTP request are shown as well. This can include any authorization headers like tokens and content type:

We can also see the request payload that has been pushed to the body of the API method. As I mentioned before, Angular converts payload key names to camel case:

That’s all for today’s post.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial