In this post, we are going to show you how to handle the PUT requests with Angular while updating the resources from the .NET Core Web API server app.

Without further ado, let’s dive right into it.

For the complete navigation and all the basic instructions of the Angular series, check out: Introduction of the Angular series.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

Folder Structure and Routing

Prior to any update action, we need to create our component files.

So, let’s create them by using the Angular CLI command which is going to create all the files and import the created OwnerUpdate component in the owner.module.ts file:

ng g component owner/owner-update --skip-tests

Now, to establish the route to this component, we have to modify the routes array in the owner routing file:

const routes: Routes = [
  { path: 'list', component: OwnerListComponent },
  { path: 'details/:id', component: OwnerDetailsComponent },
  { path: 'create', component: OwnerCreateComponent },
  { path: 'update/:id', component: OwnerUpdateComponent }
];

Now we are going to change our owner-list.component.html and the owner-list.component.ts files, to enable navigation between the OwnerList and the OwnerUpdate components:

<td><button type="button" id="update" class="btn btn-success"
  (click)="redirectToUpdatePage(owner.id)">Update</button></td>

And the .ts file:

public redirectToUpdatePage = (id) => { 
  const updateUrl: string = `/owner/update/${id}`; 
  this.router.navigate([updateUrl]); 
}

At this point, we have our routing defined, and we can move forward to handle the PUT request.

Create Form to Handle PUT Requests with Angular

Our owner-update.component.html file is going to be almost the same as the HTML file for creating the owner. Since that’s the case, let’s start with the implementation.

Firstly, let’s add the wrappers code in the owner-update.component.html file:

<div class="container-fluid">
  <form [formGroup]="ownerForm" autocomplete="off" novalidate (ngSubmit)="updateOwner(ownerForm.value)">
    <div class="card card-body bg-light mb-2 mt-2">
      
    </div>
  </form>
</div>

We already know from the previous post that the formGroup is going to contain all of the controls inside its value. This value is exactly what we send as a parameter to the updateOwner action.

Secondly, we are going to create our controls between the card card-body div tag:

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<
<div class="row mb-3">
  <label for="name" class="col-form-label col-md-2">Name of the owner: </label>
  <div class="col-md-5">
    <input type="text" formControlName="name" id="name" class="form-control" />
  </div>
  <div class="col-md-5">
    <em *ngIf="validateControl('name') 
    && hasError('name', 'required')">Name is required</em>
    <em *ngIf="validateControl('name') 
    && hasError('name', 'maxlength')">Maximum allowed length is 60 characters.</em>
  </div>
</div>

<div class="mb-3 row">
  <label for="dateOfBirth" class="col-form-label col-md-2">Date of birth: </label>
  <div class="col-md-5">
    <input type="text" formControlName="dateOfBirth" id="dateOfBirth" 
    class="form-control" readonly bsDatepicker/>
  </div>
  <div class="col-md-5">
    <em *ngIf="validateControl('dateOfBirth') 
    && hasError('dateOfBirth', 'required')">Date of birth is required</em>
  </div>
</div>

<div class="mb-3 row">
  <label for="address" class="col-form-label col-md-2">Address: </label>
  <div class="col-md-5">
    <input type="text" formControlName="address" id="address" class="form-control" />
  </div>
  <div class="col-md-5">
    <em *ngIf="validateControl('address') 
    && hasError('address', 'required')">Address is required</em>
    <em *ngIf="validateControl('address') 
    && hasError('address', 'maxlength')">Maximum allowed length is 100 characters.</em>
  </div>
</div>

Every input element contains a formControlName attribute that we are going to use in the component file for the validation. Furthermore, the validateControl and the hasError functions are the custom functions that will help us display error messages (still the same thing that we did in the CreateOwner component).

Below the last <div class="mb-3 row"> element, we are going to add the buttons:

<br><br>

<div class="mb-3 row">
    <div class="offset-5 col-md-1">
        <button type="submit" class="btn btn-info" [disabled]="!ownerForm.valid">Save</button>
    </div>
    <div class="col-md-1">
        <button type="button" class="btn btn-danger" (click)="redirectToOwnerList()">Cancel</button>
    </div>
</div>

Now we have our HTML file and it is time to implement business logic for the owner-update.component file.

Business Logic in the Component File to Handle PUT Requests with Angular

Let’s start with all the imports inside the owner-update.component.ts file:

import { Component, OnInit } from '@angular/core';
import { OwnerForUpdate } from './../../_interfaces/ownerForUpdate.model';
import { HttpErrorResponse } from '@angular/common/http';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Owner } from './../../_interfaces/owner.model';
import { OwnerRepositoryService } from 'src/app/shared/services/owner-repository.service';
import { ErrorHandlerService } from 'src/app/shared/services/error-handler.service';
import { ActivatedRoute, Router } from '@angular/router';
import { DatePipe } from '@angular/common';
import { ModalOptions, BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { SuccessModalComponent } from 'src/app/shared/modals/success-modal/success-modal.component';

Next, let’s add the required properties and inject the required services:

owner: Owner;
ownerForm: FormGroup;
bsModalRef?:BsModalRef;

constructor(private repository: OwnerRepositoryService, private errorHandler: ErrorHandlerService, 
  private router: Router, private activeRoute: ActivatedRoute, private datePipe: DatePipe,
  private modal: BsModalService) { }

To continue, we have to modify the ngOnInit function and add one additional function:

ngOnInit(): void {
  this.ownerForm = new FormGroup({
    name: new FormControl('', [Validators.required, Validators.maxLength(60)]),
    dateOfBirth: new FormControl('', [Validators.required]),
    address: new FormControl('', [Validators.required, Validators.maxLength(100)])
  });

  this.getOwnerById();
}

private getOwnerById = () => {
  const ownerId: string = this.activeRoute.snapshot.params['id'];
  const ownerByIdUri: string = `api/owner/${ownerId}`;

  this.repository.getOwner(ownerByIdUri)
  .subscribe({
    next: (own: Owner) => {
      this.owner = { ...own, 
        dateOfBirth: new Date(this.datePipe.transform(own.dateOfBirth, 'MM/dd/yyyy'))
      };
      this.ownerForm.patchValue(this.owner);
    },
    error: (err: HttpErrorResponse) => this.errorHandler.handleError(err)
  })
}

In the ngOnInit function, we instantiate the ownerForm with all the form controls and add the validation rules. Then we call the getOwnerById function to fetch the owner with the exact id from the server.

Inside this function, we execute familiar actions. Pull the id from the URI and create the API URI string, send the GET request, and process the response whether it is a success or an error response.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

One thing to pay attention to is converting the dateOfBirth value to the format we expect inside our input control. We use DatePipe to transform the format.

Now, we have to add our error validation functions:

validateControl = (controlName: string) => {
  if (this.ownerForm.get(controlName).invalid && this.ownerForm.get(controlName).touched)
    return true;
  
  return false;
} 

hasError = (controlName: string, errorName: string) => {
  if (this.ownerForm.get(controlName).hasError(errorName))
    return true;
  
  return false;
}

These are the familiar functions for validating the input fields.

Now, before we send the PUT request, we are going to add one more interface:

export interface OwnerForUpdate{
  name: string; 
  dateOfBirth: string; 
  address: string;
}

And also modify the updateOwner function inside the owner-repository.service file:

public updateOwner = (route: string, owner: OwnerForUpdate) =>

Finally, we are going to return to our component file, and execute the update action:

public updateOwner = (ownerFormValue) => {
  if (this.ownerForm.valid)
    this.executeOwnerUpdate(ownerFormValue);
}

private executeOwnerUpdate = (ownerFormValue) => {
  const ownerForUpd: OwnerForUpdate = {
    name: ownerFormValue.name,
    dateOfBirth: this.datePipe.transform(ownerFormValue.dateOfBirth, 'yyyy-MM-dd'),
    address: ownerFormValue.address
  }

  const apiUri: string = `api/owner/${this.owner.id}`;

  this.repository.updateOwner(apiUri, ownerForUpd)
  .subscribe({
    next: (_) => {
      const config: ModalOptions = {
        initialState: {
          modalHeaderText: 'Success Message',
          modalBodyText: 'Owner updated successfully',
          okButtonText: 'OK'
        }
      };

      this.bsModalRef = this.modal.show(SuccessModalComponent, config);
      this.bsModalRef.content.redirectOnOk.subscribe(_ => this.redirectToOwnerList());
    },
    error: (err: HttpErrorResponse) => this.errorHandler.handleError(err)
  })
}

This is the pretty much same logic as for the createOwner function. We just don’t have any parameters for the next: property because our API doesn’t return an object as a response to the PUT request.

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and get paid? >> JOIN US! <<

Of course, we also need that redirectToOwnerList function:

public redirectToOwnerList = () => {
  this.router.navigate(['/owner/list']);
}

You can give it a try and make some updates. Try to create success responses and error responses from the server to test the modal components as well. After that, you can check if the form validation works.

Conclusion

In this article, we’ve learned how to create a template (HTML) part of the update action, and also what actions we need to execute in the component (.ts) file.

In the next part of the series, we are going to write the delete part of the project, and slowly wrap the coding part of the series up.