Angular 4 CRUD Example:

It would be great to checkout branch angular-crud-app from repository https://github.com/theJavaGeek/angular.

Our objective is to be able to extend our bike app to :

  • be able to add, update and delete bikes.
  • be able to create bike on localhost:4200/bikes.
  • be able to delete bike from bikes list shown on localhost:4200/bikes.
  • be able to update bike information from bike-info page.

We need to implement angular 4 crud example as below.

  • update BikeService to write methods addBike()updateBike() and deleteBike().
  • update BikeInfoComponent to be able to navigate back and save bike details.
  • update BikesComponent to input a new bike and delete existing bikes.

Let’s get started with code.

bike.service.ts:

import { Injectable } from '@angular/core';
 
import { Headers, Http } from '@angular/http';
 
import 'rxjs/add/operator/toPromise';
 
import { Bike } from './bike'
 
 
@Injectable()
 
export class BikeService {
 
  constructor(private http: Http) {
 
  }
 
  private headers = new Headers({ 'Content-Type': 'application/json' });
  private bikesUrl = 'api/bikes';
 
  getBikes(): Promise<Bike[]> {
    return this.http.get(this.bikesUrl)
      .toPromise()
      .then(response => response.json().data as Bike[])
      .catch(this.handleError);
  }
 
 
  getBike(id: number): Promise<Bike> {
    const url = `${this.bikesUrl}/${id}`;
    return this.http.get(url)
      .toPromise()
      .then(response => response.json().data as Bike)
      .catch(this.handleError);
  }
 
 
  createBike(bike: Bike): Promise<Bike> {
    return this.http
      .post(this.bikesUrl, JSON.stringify(bike), { headers: this.headers })
      .toPromise()
      .then(res => res.json().data as Bike)
      .catch(this.handleError);
  }
 
  updateBike(bike: Bike): Promise<Bike> {
    const url = `${this.bikesUrl}/${bike.id}`;
    return this.http
      .put(url, JSON.stringify(bike), { headers: this.headers })
      .toPromise()
      .then(() => bike)
      .catch(this.handleError);
  }
 
  deleteBike(bike: Bike): Promise<void> {
    const url = `${this.bikesUrl}/${bike.id}`;
    return this.http.delete(url, { headers: this.headers })
      .toPromise()
      .then(() => null)
      .catch(this.handleError);
  }
 
  private handleError(error: any): Promise<any> {
    console.error('An error occurred', error);
    return Promise.reject(error.message || error);
  }
}
  • createBike(bike: Bike):  takes a bike object as parameter and makes a http POST request to ‘api/bikes‘ with data as new bike object. Created bike is returned as Promise.
  • updateBike(bike: Bike): takes a bike object as parameter and makes http PUT request to update bike details by id.
  • deleteBike(bike: Bike): takes bike object as parameter and makes http DELETE request to delete bike by id.

bike-info.component.html:

<div *ngIf="bike">
  <md-card style="width: 30%">
    <md-card-header>
      <md-card-title>{{bike.model}} details!</md-card-title>
    </md-card-header>
    <md-card-content>
      <div><label>id: </label>{{bike.id}}</div>
      <div>
        <label>model: </label>
        <input [(ngModel)]="bike.model" placeholder="model" />
      </div>
      <div>
        <label>Manufacturer: </label>
        <input [(ngModel)]="bike.manufacturer" placeholder="manufacturer" />
      </div>
    </md-card-content>
    <md-card-actions>
      <button md-button (click)="goBack()"> Back </button>
      <button md-button (click)="updateBike()"> Update </button>
    </md-card-actions>
  </md-card>
  <div>
  • Note that we are simply showing existing bike information and providing input boxes for it to update information.
  • We have added two actions. Back action simply navigates to previous page and Update action saves newly entered bike details.

bike-info.component.ts:

import 'rxjs/add/operator/switchMap'
import { Component, OnInit, Input } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Location } from '@angular/common';

import { Bike } from '../bike';
import { BikeService } from '../bike.service'


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

  bike: Bike;

  constructor(
    private bikeService: BikeService,
    private route: ActivatedRoute,
    private location: Location
  ) { }

  ngOnInit(): void {
    this.route.params.switchMap((params: Params) => this.bikeService.getBike(+params['id']))
      .subscribe(bike => this.bike = bike);
  }
  updateBike(): void {
    this.bikeService.updateBike(this.bike);
    this.goBack();
  }
  goBack(): void {
    this.location.back();
  }

}
  • we injected Location class so that we can use its methods.
  • updateBike(): simply calls updateBike(bike : Bike) from BikeService.
  • goBack(): simply navigates to previous page.

We modified BikeInfo component bike details. i.e. Work for update is done for angular 4 crud example. Let us see how to create and delete bikes.

bikes.component.ts:

<h2>Bikes</h2>

<form>
  <md-input-container style="width:30%">
    <input mdInput [(ngModel)]="newBike.model" placeholder="Model" name="model">
  </md-input-container>
  <md-input-container style="width:30%">
    <input mdInput [(ngModel)]="newBike.manufacturer" placeholder="Manufacturer" name="manufacturer">
  </md-input-container>
  <button md-raised-button (click)="createBike(newBike)">Create Bike</button>

  <md-list>
    <md-list-item *ngFor="let bike of bikes" (click)="showInfo(bike)">
      <span>
          {{bike.model}}
      </span>
      <span>
        <button md-raised-button (click)="deleteBike(bike); $event.stopPropagation()">Delete</button>
      </span>
    </md-list-item>
  </md-list>
</form>
  • This page has a form to create bike. Button calls createBike(newBike) method from bikes.component.ts.
  • Then it shows the list of all bikes. Delete button appears in front of each bike name. On click, it will call “deleteBike(bike)” from bikes.component.ts.  $event.stopPropagation() stops the app to navigate to bike details. This is because we are technically clicking on list item and it is supposed to navigate to bike info page.

bikes.component.ts:

import { Component, OnInit } from ‘@angular/core’;
import { Router } from ‘@angular/router’;
import { Bike } from ‘../bike’;
import { BikeService } from ‘../bike.service’;

@Component({
selector: ‘app-bikes’,
templateUrl: ‘./bikes.component.html’,
styleUrls: [‘./bikes.component.css’]
})
export class BikesComponent implements OnInit {

bikes: Bike[];
selectedBike: Bike;
newBike: Bike;

constructor(private router: Router, private bikeService: BikeService) {

}

ngOnInit() {
this.bikeService.getBikes().then(bikes => this.bikes = bikes);
this.newBike = new Bike();
}

createBike(bike: Bike): void {

this.bikeService.createBike(bike)
  .then(bike => {
    this.bikes.push(bike);
    this.selectedBike = null;
  });

}

deleteBike(bike: Bike): void {
this.bikeService
.deleteBike(bike)
.then(() => {
this.bikes = this.bikes.filter(b => b !== bike);
if (this.selectedBike === bike) { this.selectedBike = null; }
});
}

showInfo(bike: Bike): void {
this.selectedBike = bike;
this.router.navigate([‘/information’, this.selectedBike.id]);
}
}

  • createBike(bike: Bike) : Invokes createBike(bike: bike) from BikeService and pushes current bike in bikes array.
  • deleteBike(bike : Bike): invokes deleteBike from BikeService and nullifies selectedBike.

Angular 4 CRUD example is almost done. We just need to modify AppModule to include animations.

app.module.ts:

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

import { AppComponent } from './app.component';
import { BikeInfoComponent } from './bike-info/bike-info.component';
import { BikesComponent } from './bikes/bikes.component';
import { BikeService } from './bike.service';

import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { BikesDatabaseService } from './bikes-database.service';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { AppRoutingModule } from './app-routing/app-routing.module';
import { MaterialModule, MdList, MdListItem } from '@angular/material'


@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    InMemoryWebApiModule.forRoot(BikesDatabaseService),
    MaterialModule.forRoot(),
    AppRoutingModule,
    BrowserAnimationsModule
  ],
  declarations: [
    AppComponent,
    BikesComponent,
    BikeInfoComponent,
  ],
  bootstrap: [AppComponent],
  providers: [BikeService],
})
export class AppModule { }

app-routing.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';

import { BikeInfoComponent } from '../bike-info/bike-info.component';
import { BikesComponent } from '../bikes/bikes.component';

const routes: Routes = [
  { path: 'information/:id', component: BikeInfoComponent },
  { path: 'bikes', component: BikesComponent }
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes)
  ],
  exports: [
    RouterModule
  ],
  declarations: []
})
export class AppRoutingModule { }