Wednesday, June 13, 2018

A4–Building App using Angular and ASP.NET Core 2.1 – Posting Form Values, Server-Side Validation, Displaying Error Messages

This post is a part of a series of posts that I am writing as I am building an app using Angular and ASP.NET Core 2.1. Links to previous posts –> A1, A2, A3, Github Repo

Today, my goal is hook up submit button for Insert, Edit and Delete Pages with HttpClient and post form values to ASP.NET Core Web API backend.

Below is code for product-insert.component.ts file, the code for product-edit.component.ts is same. Initially I am using HttpClient directly inside component.

import { Component, OnInit, Inject } from '@angular/core';
import { Product, IProduct } from '../product';
import { HttpClient, HttpHeaders } from '@angular/common/http';

const httpOptions = {
  headers: new HttpHeaders({
    'Content-Type': 'application/json',
    'Authorization': 'my-auth-token'
  })
};


@Component({
  selector: 'app-product-insert',
  templateUrl: './product-insert.component.html',
  styleUrls: ['./product-insert.component.css']
})

export class ProductInsertComponent implements OnInit {

  public product: IProduct;
  private _baseUrl: string;
  constructor(private _http: HttpClient, @Inject('BASE_URL') baseUrl: string) {
    this.product = new Product("", 0);
    this._baseUrl = baseUrl;
  }


  onProductSave(product: IProduct) {
    console.log(JSON.stringify(product));
    this._http.post<Product>(this._baseUrl + 'api/Product/Insert', product, httpOptions)
      .subscribe(result => {
        console.log(result);
      }, error => console.log(error));
    
  }
  ngOnInit() {

  }

}

I tested that when I clicked Save button, http post is getting called and same object being returned. In the image below, you can see that first json object is from component and other one is returned from API call.

image

I want to add some server side validation. Let’s say that price of product should be greater than 100. If price is less than 100 then throw BadRequest from server and display to user that error.

  [HttpPost("[action]")]
        public ActionResult<Product> Insert([FromBody] Product product)
        {
            _validateProduct(product);
            if (ModelState.IsValid)
            {
                //TODO: Save Product to database.
                return product;
            }
            return BadRequest(ModelState);
        }

        private void _validateProduct(Product product)
        {
            if (product.Price < 100)
            {
                ModelState.AddModelError(nameof(Product.Price), $"Product price cannot be less than 100, you entered {product.Price}");
            }
        }

In the code above, if the product price is less than 100, I am adding model error for key Price and message. This message we will have to show it to the user.

import { Component, OnInit, Inject } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';
import { Product, IProduct } from '../product';

const httpOptions = {
  headers: new HttpHeaders({
    'Content-Type': 'application/json',
    'Authorization': 'my-auth-token'
  })
};


@Component({
  selector: 'app-product-edit',
  templateUrl: './product-edit.component.html',
  styleUrls: ['./product-edit.component.css']
})
export class ProductEditComponent implements OnInit {

  public errors: string[];
  public product: Product;
  id: number;
  private _baseUrl: string;
  constructor(private _http: HttpClient, private _activeRoute: ActivatedRoute, @Inject('BASE_URL') baseUrl: string) {
    this._baseUrl = baseUrl;
    this.id = this._activeRoute.snapshot.params['id'];
    this._http.get<Product>(baseUrl + 'api/Product/Find/' + this.id).subscribe(result => {
      this.product = result;
    }, error => console.log(error));


  }

  onProductSave(product: IProduct) {
    console.log(JSON.stringify(product));
    this._http.post<Product>(this._baseUrl + 'api/Product/Edit', product, httpOptions)
      .subscribe(result => {
        console.log(result);
      }, error => {
        console.log(error.error);
        if (error.status === 400) {
          let allErrors = error.error;
          console.log(allErrors);
          for (var fieldName in allErrors) {
            if (allErrors.hasOwnProperty(fieldName)) {
              this.errors.push(allErrors[fieldName]);
            }
          }
        }
      });
  }

  ngOnInit() {
    this.errors = [];
  }

}

I found this post by Carl and I am following his post to show error messages returned. Thank you Carl.

<br />
  <div class="alert alert-danger" role="alert" *ngIf="errors.length > 0">
    <ul>
      <li *ngFor="let error of errors">
        {{ error }}
      </li>
    </ul>
  </div>

This is displayed as shown in the image below.

image

I am displaying server side error message like below and marking the control as invalid as shown below.

import { Component, OnInit, Inject } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';
import { Product, IProduct } from '../product';
import { FormControl, AbstractControl } from '@angular/forms';

const httpOptions = {
  headers: new HttpHeaders({
    'Content-Type': 'application/json',
    'Authorization': 'my-auth-token'
  })
};


@Component({
  selector: 'app-product-edit',
  templateUrl: './product-edit.component.html',
  styleUrls: ['./product-edit.component.css']
})
export class ProductEditComponent implements OnInit {

  public errors: string[];
  public product: Product;
  id: number;
  private _baseUrl: string;
  constructor(private _http: HttpClient, private _activeRoute: ActivatedRoute, @Inject('BASE_URL') baseUrl: string) {
    this._baseUrl = baseUrl;
    this.id = this._activeRoute.snapshot.params['id'];
    this._http.get<Product>(baseUrl + 'api/Product/Find/' + this.id).subscribe(result => {
      this.product = result;
    }, error => console.log(error));


  }

  onProductSave(productForm: any) {
    console.log((productForm));
    productForm.controls;
    let product = productForm.value;
    
    this._http.post<Product>(this._baseUrl + 'api/Product/Edit', product, httpOptions)
      .subscribe(result => {
        console.log(result);
      }, error => {
        console.log(error);
        console.log(error.error);
        if (error.status === 400) {
          let allErrors = error.error;
          for (var fieldName in allErrors) {
            if (allErrors.hasOwnProperty(fieldName)) {
              if (productForm.controls[fieldName]) {
                productForm.controls[fieldName].markAsTouched();
                productForm.controls[fieldName].setErrors({ invalid: true });
                this.errors.push(allErrors[fieldName]);
              } 
            }
          }
        }
      });
  }

  ngOnInit() {
    this.errors = [];
  }

}

This way the field is marked as invalid. I would like to show message below the field but it is taking longer for me to figure out today.

image

So that’s it folks for today, you can find the code on Github.

Next goal is to extract httpclient into a product service. And finish delete page.