Angular 2 CRUD with Web API 2

angular 2 crud application example angular 2 crud rest api angular 2 web api tutorial angular 2 crud github angular 2 crud operations angular 2 crud operations using http angular 4 insert update delete with web api angular 2 mvc 4 example

Angular 2 insert update, delete, search, sort feature we will try in this article which we will improve with dynamic component loading and image upload with drag and drop feature in coming articles with the same base code. We are going to use Angular-CLI 4 for this application.

What we need to start:

  • Node version 6.x
  • Any code editor, I use Visual Studio Code, it is free
  • Angular CLI
  • Bootstrap 3.x

Let's create the application step by step, Skipping step of installations, hope you can do it by yourself:

  • Open VS Code
  • From view menu open "Integrated Terminal"
  • Change the folder where we want to create the project
  • run: ng new crud-webapi --ng4

It will take a little bit time and create and configure our project. Now open the folder crud-webapi in Visual Studio Code.

Open integrated terminal and try to build by using ng build if successful, try ng serve -o, it will run the application in browser.

Customer List Component

Stop the application and let's create our very first component in Angular 2, change the path to src/app by using cd src/app now run the following command

ng generate component CustomerList

It will create four files in a folder named customer-list in app folder

  • customer-list.component.css
  • customer-list.component.html
  • customer-list.component.spec.ts
  • customer-list.component.ts Customer list- fetch, search sort, insert, update, delete Delete .css and .spec.ts because we are not write any code testing and component based css. After deleting open .component.ts and remove the line styleUrls: ['./customer-list.component.css'] from @component meta-data.

It's nice and handy tool which does lots of thing for us automatically, it already register our component with module, open app.module.ts and note our component is imported and added to declare section with name "CustomerListComponent".

Let's add a heading on the page to show and check, add <h1>Customer List</h1>, run the application it will not show anywhere even if we will try to browse /customerlist, because we have not done routing yet.

If you will note in our customer-list.component.ts file we have meta-data selector: 'app-customer-list', it means if what want to use this component on other page we can use the tag <app-customer-list></app-customer-list> and this page will render there, so let's add this tag to our app.component.html, remove everything there and add

<app-customer-list></app-customer-list>

Now run the application and we will see our heading, well.

We want to use the bootstrap css for design so let's add it to our application. Open package.json and add at the end "bootstrap": "^3.3.7"

Stop application and run npm install, it will install all the files, we can use bootstrap css on index page but we want it to be combined and minified with other css file, so open .angular-cli.json file and change the style section to

"styles": [
    "styles.css",
    "../node_modules/bootstrap/dist/css/bootstrap.min.css"
],

To check, we add container class of bootstrap on app.component.html page like this

<div class="container">
  <app-customer-list></app-customer-list>
</div>

Build and run the application and notice it is applying the class.

To show the list of customers, we need data, I have already created a Table, EntityFromework and Web API to get the data:

Customer Table Structure to Read, Insert, Update Delete:

CREATE TABLE [Customers](
    [Id]   [int] IDENTITY(1,1) NOT NULL Primary Key,
    [Name] [varchar](50) NOT NULL,
    [Address] [varchar](200) NOT NULL,
    [Phone] [varchar](10) NOT NULL,
    [Image] [varchar](200) NULL
)

I keep it simple so we can more focus on features than data.

Web API method to get the customers from database

[HttpGet]
[Route("api/Customer/GetCustomers")]
public IHttpActionResult GetCustomers()
{
    var db = new CustomerEntities();
    var customers = db.Customers.ToList();
    return Ok(customers);
}

Here I used two Web API 2.0 new features, attribute routing and IHttpActionResult as return type.

We cannot access Restful services from other location, this is the limitation of client side application, it will give error of CORS, so I enabled my Web API to allow CORS by adding following code in my App_Start/WebApiConfig.css file

var cors = new EnableCorsAttribute("http://localhost:4200", "*", "*");
config.EnableCors(cors);

I also added some other fomatting like return json, camelCasing:

config.Formatters.JsonFormatter.SupportedMediaTypes.Add(
            new MediaTypeHeaderValue("text/html"));
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver     = new CamelCasePropertyNamesContractResolver();

Now our Web API is ready to use, time to write code for service in angular to get data from database by using above Web API, create a new folder called Services in src/app folder, where we will keep our all the services, chane the folder in integrated terminal and run command

ng generate service Customer

Expand the service folder, our basic service structure is created, change the code as follows:

import { Injectable } from '@angular/core';
import { Http, Response} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

@Injectable()
export class CustomerService {
      baseUrl: string = 'http://localhost:33699/api/customer/'

    constructor(private _http: Http) { }

    getCustomers(){
      return this._http.get(this.baseUrl + 'getcustomers')
                .map((response: Response) =>response.json())
                .catch(this._errorHandler);
    }

    _errorHandler(error:Response){
      debugger;
      console.log(error);
      return Observable.throw(error || "Internal server error");
    }
}

It has lots of code so let's see one by one:

  • Many imports which we are going to use in our code
  • @Injectable(): it maintain our dependencies wherever we will use this service
  • baseUrl: get the code after running the web api
  • Created Http object as private in constructor, suggest way
  • getCustomers function to call the web api method
  • Convert response into json
  • An error handler if something goes wrong

To use this service in our component, there are two way either we can use providers in component or in app.module. Problem with first approach is, service will not be singleton any more, and every component will be creating its own object of service. Let's use it in our app.module.ts

//import on top
import { CustomerService } from './services/customer.service'
// In  @NgModule
.........
providers: [CustomerService],

Now add in customer.list.compoent.ts to use this service, import and create object of it in constructor:

import { CustomerService } from '../services/customer.service';
......
constructor( private _customerService : CustomerService) { }

Now we are ready to use this service in component, we add two varaibles 1. customers: Array to hold customers and 2. errorMessage to hold the error and show on page and create a new function getCustomers in our component

customers: Array<any> = [];
errorMessage: any;

getCustomers(){
    this._customerService.getCustomers().subscribe(
        data => this.customers = data,
        error => this.errorMessage = error
    )
}
// call this function in ngOnInit to call on page loading
ngOnInit() {
    this.getCustomers()
}

Our component is ready to fetch the data from database so time to add html to show the records, please update cusotmer-list.component.html with following code:

<h1>Customer List
  <button class="btn btn-success btn-circle pull-right" 
          (click)="add()"     
          title="Add Customer">
    <i class="glyphicon glyphicon-plus"></i>
  </button>
</h1>

<div class="alert-danger">{{errorMessage}}</div>

<div class="input-group">
    <input type="text" class="form-control" 
            placeholder="Search" 
            aria-describedby="basic-addon1">
    <span class="input-group-addon" id="basic-addon1">Search</span>
</div> 
<table class="table table-bordered">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Address</th>
            <th>Phone</th>
            <th>Action</th>
        </tr>
    </thead>
    <tbody>
        <tr *ngFor="let cust of customers">
            <td>{{cust.id}}</td>
            <td>{{cust.name}}</td>
            <td>{{cust.address}}</td>
            <td>{{cust.phone}}</td>
            <td style="width: 70px;">
                <i class="glyphicon glyphicon-edit pointer"
                   (click)="edit(cust.id)" title="Edit"></i>
                <i class="glyphicon glyphicon-trash pointer"    
                    (click)="delete(cust.id)" title="Delete"></i>
                <i class="glyphicon glyphicon-user pointer" 
                    (click)="image(cust.id)" title="Image"></i>
            </td>
        </tr>
    </tbody>
</table>

In this HTML nothing is special except *ngFor="let cust of customers", which will iterate customers list and show row by row.

We added some features like add, edit, delete and image button which we will use in future. If you note, there is no code of these buttons just bootstrap icons to show. For search and sort see my previous blog Angular 2 Search and Sort with ngFor repeater with example.

Customer Add/Edit Component

Let’s create customer page to add & edit customer detail, we are not going to see upload image now but sure we will do it latter in more detail. Create component by using command

ng g component Customer

insert, update customer with web api
We are going to use the ReactiveFormsModule for this page which is a new way to design pages in angular 4, so we need to update our app.module.ts file, open it and update file as follows:

....
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
....
import { CustomerComponent } from './components/customer/customer.component'

declarations: [...
         CustomerComponent]
imports: [...
    ReactiveFormsModule] 

Time to create the form model by using form builder, for this we need to import some classes to create our form, open customer.componrnt.ts file and add these imports and codes:

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators} from '@angular/forms';
@Component({ .. })
export class CustomerComponent implements OnInit {
    customerForm: FormGroup;        
    title: string = "Add";
    constructor(private _fb: FormBuilder) { 
        this.customerForm = this._fb.group({
          id: 0,
          name: ['', [Validators.required,
                      Validators.minLength(3),
                      Validators.maxLength(30)]],
          address: ['', [Validators.required]],
          phone: ['', [Validators.pattern("[1-9][0-9]{9}")]],
          image:''
        })
    }
    // we will use it to load data for update
    ngOnInit() {
    }
}

We imported FormBuilder, FormGroup, Validators to create the model. First we create customrForm of type FormGroup. We will use Validators to add different kind of validations like, required, maxlenght, minlength etc. latter in this article.

See the constructor, we created an object _fb of FormBuilder. Then we added a FormGroup to our customerForm and added different controls

Let's see the name control in detail:

name: ['', [Validators.required,
        Validators.minLength(3),
        Validators.maxLength(30)]],
  • First argument is default value "" (blank)
  • Second parameter is an array
  • This array we use to validate, required, min and max length
  • Phone is not required, but have a pattern of number, must start with a non-zero number and should be of total 10 digits.
  • image: I added it, so easily I can load the data from database to page, and in future we will add image as well on the page

ngOnInit: Lifecycle hook that is called after a component's constructor.

To edit a record we will use this method to get the data by id from database and populate on the page, we will see it latter.

Let's add the HTML for the customer page, here is the complete code:

<h1>{{title}} Customer</h1>
<hr>

<form [formGroup]="customerForm" (ngSubmit)="save()" novalidate>
    <div class="form-group row">
        <label for="" class="col-md-12">Name</label>
        <div class="col-md-4">
            <input type="text" class="form-control"
                   formControlName="name">
        </div>
        <div class="col-md-6"></div>
    </div>
    <div class="form-group row">
        <label for="" class="col-md-12">Address</label>
        <div class="col-md-4">
            <input type="text" class="form-control"
                    formControlName="address" >
        </div>
        <div class="col-md-6"></div>
    </div>
    <div class="form-group row">
        <label for="" class="col-md-12">Phone#</label>
        <div class="col-md-4">
            <input type="text" class="form-control"
                   formControlName="phone">
        </div>
        <div class="col-md-6"></div>
    </div>
    <div class="form-group row">
      <div class="col-md-12">
         <input type="submit" 
                class="btn btn-primary" 
                value="Save" />
         <input type="button" 
                class="btn btn-warning" 
                value="Cancel"
                (click)="cancel()">
      </div>

    </div>
</form>
  • form tag have attribute [formGroup]="customerForm", it is our defined form group
  • (ngSubmit)="save()": on submit it will call save method, we will add next
  • every text box have attribute formControlName="xyz", this is the way we can bind the control with HTML

To see our validation is working ot not, add following css in style.css file:

input[type='text'].ng-invalid{
    border-left: 4px solid #900;
}

input[type='text'].ng-valid{
    border-left: 4px solid #090;
}

To run this page we need some routing, so lets add it, open app.module.ts and update:

......
import { RouterModule } from '@angular/router';

imports: [....
  ReactiveFormsModule,
  RouterModule.forRoot([
    { path: "", redirectTo: "home", pathMatch: 'full' },
    { path: "home", component: HomeComponent },
    { path: "customers", component: CustomerListComponent },
    { path: "customers/add", component: CustomerComponent },
    { path: "customers/edit/:id", component: CustomerComponent },     
    { path: '**', component: PageNotFoundComponent }
  ])
],
  • if blank, then use the home page
  • home: created routing for home user which is using HomeComponent
  • customers: to show the customer list page see the component
  • customers/add and customers/edit/:id both use the same component
  • **: if no route match then show page not found (create your own)

We added route for home, customers, add, and edit, open the customer list page and update the link of different buttons like add and edit

// To Add button
(click) = 'add()'
// In side the table for edit button
(click)="edit(cust.id)"

Need to add two methods but to redirect we need to import router so complete change will be like this:

import { Router } from '@angular/router';
.....
constructor( private _customerService : CustomerService,
           private _router: Router) { }
.....
add(){
    this._router.navigate(['customers/add']);
}
edit(id){
    this._router.navigate(['customers/edit/'+ id])
}

Run and try click on add and edit button in grid, it will redirect smoothly to the desired url and will show the customer page to add the customer.

Now we need to change the code so if Id is passed then pull the record and show in controls to edit. First of all we well add the method in our Web API to get customer data from database to update then we will add a service to use this method from our Angular application, let's write both together:

[HttpGet]   
[Route("api/Customer/GetCustomerById/{id}")]
public IHttpActionResult GetCustomerById(int id)
{
    var customer = db.Customers.FirstOrDefault(x => x.Id == id);
    if (customer != null)
        return Ok(customer);
    else
        return NotFound();
}

// add a method in customer.service.ts file like this:  
getCustomerById(id){
  return this._http.get(this.baseUrl +"GetCustomerById/"+ id)
          .map((response: Response) => response.json())
          .catch(this._errorHandler)
}

We need to read the passed id from customer list to edit the record, do you know how to read the id, we need to import ActivatedRoute, let's write the code:

import { ActivatedRoute } from '@angular/router';
import { CustomerService } from '../../services/customer.service';
.....
// Some variable which we will need
title: string = "Add";
id: number = 0;
errorMessage: any;

constructor(private _fb: FormBuilder, 
    private _avRoute: ActivatedRoute,
    private _customerService: CustomerService) { 
    // Check if id is passed and read it
    if(this._avRoute.snapshot.params["id"]){
      this.id = parseInt( this._avRoute.snapshot.params["id"]);
      console.log(this.id);
    }
    .......
}

ngOnInit() {
  if(this.id > 0){
    this.title = 'Edit';
    this._customerService.getCustomerById(this.id)
      .subscribe(resp => this.customerForm.setValue(resp)
               , error => this.errorMessage = error);         
  }
}

Note the constructor, we use snapshot to read the params, and it will work in this scenario because every time user will come from another page or type id in URL and hit enter to refresh the page. If we would have some button on the page to change the id, it would not read next time, so in that case we must need to use the observable one.

ngOnInit(): If id is passed then we change the title to "Edit" and read data from database for that id. What ever we read from database (see the JSON) have all the properties same as we declared in our FormBuilder so simply we use the set value to the form by using

this.customerForm.setValue(resp):
// json from database
{
    "id":1,
    "name":"Albert Einstein",
    "address":"123 Queens Long Island New York",
    "phone":"9876543201",
    "image":null
}

Both add and edit button will work smooth, need to save the data back to database. Now we need to add the code to show the validation errors when user click save button, we will add save code latter.

If you remember, we added three type of validation, 1) required, 2) minimum length 3 character and 3) maximum 30 characters. We will add the validation in a way that it can show only on changing the value (dirty or form submitted), here is complete validation for the name field:

<div class="col-md-6">
    <div class="error" *ngIf="!customerForm.controls['name'].valid && (customerForm.controls['name'].dirty || submitted)">
        <div *ngIf="customerForm.controls['name'].hasError('required')">
            Name is required
        </div>
        <div *ngIf="customerForm.controls['name'].hasError('minlength')">
           Min. 3 charaters
        </div>
        <div *ngIf="customerForm.controls['name'].hasError('maxlength')">
            Must not exceed 30 charaters
        </div>
    </div>
</div>

If you can see, there are too much code to add in html ,customerForm.controls['name'].hasErros('required'), we can convert it in name.errors.required only, by adding following line to the component.ts file as getter function

get name() { return this.customerForm.get('name'); }
// similarly for address and phone
get address() { return this.customerForm.get('address'); }
get phone() { return this.customerForm.get('phone'); }

Now the name validation code will be like this:

<div class="col-md-6">
<div class="error" 
     *ngIf="name.invalid && (name.dirty || submitted)">
    <div *ngIf="name.errors.required">
        Name is required
    </div>
    <div *ngIf="name.errors.minlength">
       Min. 3 charaters
    </div>
    <div *ngIf="name.errors.maxlength">
        Must not exceed 30 charaters
    </div>
</div>
</div>

For phone pattern validation add following code in html

<div class="col-md-6">
    <div class="error" 
         *ngIf="phone.invalid && (phone.dirty || submitted)">
        <div *ngIf="phone.errors.pattern">
            Invalid phone number!
        </div>
    </div>            
</div>

Now let's add the code in Web API to save the customer into database and return currently added/updated customer Id, note the same method we are using for both insert new record as well as to update an existing customer, if customer id is greater than zero (0):

[HttpPost]
[Route("api/Customer/SaveCustomer")]
public IHttpActionResult SaveCustomer(Customer customer)
{
    if (customer.Id > 0)
    {
        var dbCustomer = db.Customers.FirstOrDefault(x=>x.Id == customer.Id);
        dbCustomer.Name = customer.Name;
        dbCustomer.Address = customer.Address;
        dbCustomer.Phone = customer.Phone;
        db.SaveChanges();
    }
    else
    {
        db.Customers.Add(customer);
        db.SaveChanges();
    }
    return Ok(customer.Id);
}

To use this Web API method, either we can directly use it in our component or best to use it in our service, so in future if we need to use this method from any other component, directly use this method by importing the service, here is the method in customer.service.ts

saveCustomer(customer){
  return this._http.post(this.baseUrl + 'savecustomer', customer)
          .map((response: Response) => response.json())
          .catch(this._errorHandler)
}

Nothing special, simply using Http service and passing customer object as body parameter.

Let's add the save method in our customer component, when user can type and click save button to save data, after successfully add/updated, we will return user to customers list page and we will try to pass the added/updated customer ID so we can highlight the current customer record:

save(){ 
    this.submitted = true;
    if(!this.customerForm.valid){
        return;
    }

    this._customerService.saveCustomer(this.customerForm.value)
    .subscribe(custId => {
        //alert('Saved Successfully!')
        this._router.navigate(['customers', {id: custId}]);
     }, error => this.errorMessage = error )
}

- this.submitted: we are indicating that form is submitted, so if there is any invalid value on the page, start showing error - !this.customerForm.valid: anything invalid on the page, return user back to the page and show error - Call the service save method by passing user entered values by using this.customerForm.value - If you remember, we are not accepting any parameter with customers, but we can pass any number of optional parameter by using { key: value, ...}, that's what we are using here

Since we are passing current customer id back to the list page to highlight, so lets add some code there to do so. First of add ActivatedRoute with Router and in constructor _activatedRoute :

import { Router, ActivatedRoute } from '@angular/router';
.....
 constructor( private _customerService : CustomerService,
           private _router: Router,
           private _activatedRoute: ActivatedRoute) { }

// Add a variable to hold the value
currentId: number = 0;
//in ngOnInit add following lines   
if(this._activatedRoute.snapshot.params["id"])
  this.currentId = parseInt(this._activatedRoute.snapshot.params["id"]);

We need to change html to apply the btn-primary bootstrap class conditionally, means check if current customer id and current id is same, then add following code in tr, where we are using ngFor:

<tr *ngFor="let cust of customers"
    [ngClass]="{'btn-primary': cust.id == currentId }">

Till now, we did not added cancel method to customer component, add this code:

cancel(){
    this._router.navigate(["customers", {id: this.id}]);
}

Two more things, delete and image, let's add the code for delete, here is the delete code in Web API:

[HttpDelete]
[Route("api/Customer/DeleteCustomer/{id}")]
public IHttpActionResult DeleteCustomer(int id)
{
    var customer = new Customer { Id = id };
    db.Customers.Attach(customer);
    db.Customers.Remove(customer);
    db.SaveChanges();
    return Ok(id);
}

Add service method to call this web API delete method from Angular

deleteCustomer(id){
  return this._http.delete(this.baseUrl + "DeleteCustomer/" + id)
            .map((response:Response) =>  response.json())
            .catch(this._errorHandler)
}

We need to add the delete method in customer-list.component.ts as well as in HTML to click event, after successful deletion, we will not fetch the record again from database but simply we will find the record in our customers variable and remove that object from the list by using simply JavaScript findIndex and splice. But before deleting, we will take confirmation form use by using JavaScript confirm:

// In HTML 
(click)="delete(cust.id)"
// In component

delete(id){
   var ans = confirm("Do you want to delete customer with Id: " + id);
   if(ans){
     this._customerService.deleteCustomer(id)
      .subscribe(  data=> {
        var index = this.customers.findIndex(x=>x.id == id);
        this.customers.splice(index, 1);
      }, error=> this.errorMessage = error )
   }
}

We completed now:

  • Fetch data from database and show into a grid form by using table
  • Use routing to show different URL for different pages
  • Use ReactiveFormsModule and FormBuilder to create page
  • Add new customer by using Web API and SQL Server
  • Update existing customer by using Web API & SQL Server
  • Cancel add/update
  • Highlight added/update customer record
  • Delete a customer with confirmation box
  • Update list without refreshing data from database

Code can be downloaded from Github

In our next article, we will try to use modal popup to show Add/Edit/Delete confirmation page without using any third party library.

Ali Adravi Having 13+ years of experience in Microsoft Technologies (C#, ASP.Net, MVC and SQL Server). Worked with Metaoption LLC, for more than 9 years and still with the same company. Always ready to learn new technologies and tricks.
  • angular2
  • Angularjs
  • Web API
By Ali Adravi On 03 Sep, 17  Viewed: 538

Other blogs you may like

Angular component communication by using Input Output decorator

@Input and @Output decorator ar used to pass data/communicate between components, @input is used to pass data from parent to child component on the other hand @Output is use to notify the parent by using EventEmitter. Take the simple example of customers list parent page and add/edit child... By Ali Adravi   On 30 Sep 2017  Viewed: 109

Angular 4 upload files with data and web api by drag & drop

Upload file with data by clicking on button or by drag and drop, is the topic of this article. We will try to create an image gallery by uploading files and save data into database and images into a folder. We will try to create a component in a way that it can be used anywhere in entire... By Ali Adravi   On 24 Sep 2017  Viewed: 638

Angular 2 Dynamically Add & remove Components

Angular 2 gives full control to add or remove component in other component programmatically by simple steps. In this article we are going to see how to add, remove experience component to any other component, we will also try to pass some values to component from parent to child component to see if... By Ali Adravi   On 27 Aug 2017  Viewed: 527

Angular 2 Accordion expand only one at a time

Createing accordion demo in Angular 2 is more than easy, Angular have rich of conditional features which we will see in this article, like how to apply more than one class on the basis of some conditional values. For this demo we are not goign to write some fancy CSS and animation but use the... By Ali Adravi   On 19 May 2017  Viewed: 2,027

Angular 2 Page Title Static and Dynamic with built-in Title Service

Angular 2 have a built-in service called Title, while in angularjs or angular 1 we were maintaining it through the routing or through the service. By using the Title service provided by Angular 2, in 2 minute every thing will be set without any complication, even we don't need to understand the... By Ali Adravi   On 14 Apr 2017  Viewed: 1,317