Angular CRUD 1: in-memory-web-api installation and configuration

This post, the first in a series, is aimed at developers wanting to learn how to perform Create Read Update Delete (CRUD) operations from Angular.

In order to perform CRUD operations from Angular, back end environments and a database are required. Setting up an environment like that is not a trivial undertaking so instead I will be using the Angular in-memory-web-api which allows the focus to remain on learning CRUD operations without going on a yak shaving exercise in trying to set up and install various back end components.

What is the in-memory-web-api?

The in-memory-web-api provides a configurable faux back end for your project. It intercepts HTTP requests and redirects them to a database under your control.

Whilst this series of posts will focus on learning CRUD operations, the in-memory-web-api has many more use cases.

Installation

Create a new Angular application. I have called mine columbo-crud

ng new columbo-crud

Choose Y for adding Angular Routing and CSS for the stylesheet formatting.

Install the in-memory-web-api:

npm i angular-in-memory-web-api -D

The -D flag ensures that this is only a dependency in the development environment.

Configuration

With the the in-memory-web-api installed, a number of supporting objects can now be created.

Class

ng generate class classes/villain
export class Villain {    
    constructor(public id = 0, public name = '', public episode = '' ) { }    
}

The Villain class is the blueprint for the data available about a Columbo villain; a primary key (id) along with their name and which episode they appeared in.

Services

ng g s services/villain-in-mem-data
import { Injectable } from '@angular/core';
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Villain } from '../classes/villain';

@Injectable({
  providedIn: 'root'
})
export class VillainInMemDataService implements InMemoryDbService {
  createDb() {
    let villains: Villain[] = [
      { id: 1, name: 'Dr Ray Flemming', episode: "Prescription Murder" },
      { id: 2, name: 'Dale Kingston' , episode: "Suitable for Framing" },
      { id: 3, name: 'Beth Chadwick' , episode: "Lady in Waiting"},
      { id: 4, name: 'Emmett Clayton' , episode: "The Most Dangerous Match"},
      { id: 5, name: 'Viveca Scott' , episode: "Lovely but Lethal"},
      { id: 6, name: 'Dr Bart Keppel' , episode: "Double Exposure"},
      { id: 7, name: 'Milo Janus' , episode: "An Exercise in Fatality"},
      { id: 8, name: 'Harold Van Wick' , episode: "Playback"},
      { id: 9, name: 'Adrian Carsini' , episode: "Any Old Port in a Storm"},
      { id: 10, name: 'Abigail Mitchell', episode: "Try and Catch Me" },
    ];  
    return {villains};   
  }  
}

The service VillianInMemDataService implements InMemoryDbService interface. This example meets the minimum requirements for the interface which is the createDb() method.

This method creates a “database” containing the initial values of the Columbo villains and the name of the episode they appeared in. At the time of writing the maintainers of in-memory-web-api assume that every collection has a primary key called id

ng g s services/villain
import { Villain } from '../classes/villain';
import { Observable } from 'rxjs';

export abstract class VillainsService {
  villainsUrl = 'api/villains';

  abstract getVillains(): Observable<Villain[]>;
  abstract getVillain(id: number): Observable<Villain>;
  abstract addVillain(name: string, episode: string): Observable<Villain>;
  abstract deleteVillain(villain: Villain | number): Observable<Villain>;
  abstract searchVillain(term: string): Observable<Villain[]>;
  abstract updateVillain(villain: Villain): Observable<Villain>;

}

Here is an abstract class that defines which operations we want to be able to perform on our “database” such as listing all the villains (getVillains) or adding a new one (addVillain)

http-client-villain.service.ts

ng g s services/http-client-villain
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';

import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Villain } from '../classes/villain';
import { VillainsService } from './villain.service';

const cudOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json'})};

@Injectable()

export class HttpClientVillainService extends VillainsService {
  
  constructor(private http: HttpClient) {
    super();
   }

  getVillains(): Observable<Villain[]> {
    return this.http.get<Villain[]>(this.villainsUrl).pipe(
      catchError(this.handleError)
    );
  }

  // get by id - will 404 when id not found
  getVillain(id: number): Observable<Villain> {
    const url = `${this.villainsUrl}/${id}`;
    return this.http.get<Villain>(url).pipe(
      catchError(this.handleError)
    );
  }

  addVillain(name: string, episode: string): Observable<Villain> {
    const villain = { name, episode };

    return this.http.post<Villain>(this.villainsUrl, villain, cudOptions).pipe(
      catchError(this.handleError)
    );
  }

  deleteVillain(villain: number | Villain): Observable<Villain> {
    const id = typeof villain === 'number' ? villain : villain.id;
    const url = `${this.villainsUrl}/${id}`;

    return this.http.delete<Villain>(url, cudOptions).pipe(
      catchError(this.handleError)
    );
  }

  searchVillain(term: string): Observable<Villain[]> {
    term = term.trim();
    // add safe, encoded search parameter if term is present
    const options = term ?
    { params: new HttpParams().set('name', term)} : {};

    return this.http.get<Villain[]>(this.villainsUrl, options).pipe(
      catchError(this.handleError)
    );
  }

  updateVillain(villain: Villain): Observable<Villain> {
    return this.http.put<Villain>(this.villainsUrl, villain, cudOptions).pipe(
      catchError(this.handleError)
    );
  }
  
  private handleError(error: any) {
    console.error(error);
    return throwError(error);    
  }

}

This service implements the methods of the VillainsService abstract class. Each operation is an http request that will be handled by the in-memory-web-api

All the CRUD operations implemented so at this point you can go off and build your own user interface on top of this service. You will see mine in the accompanying series of blog posts.

Changes to app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import { HttpClientModule } from '@angular/common/http';
import { environment } from 'src/environments/environment';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { VillainInMemDataService } from './services/villain-in-mem-data.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    environment.production ?
    [] : InMemoryWebApiModule.forRoot(VillainInMemDataService)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }


The main change to app.module.ts is the code shown below:

...
environment.production ?
    [] : InMemoryWebApiModule.forRoot(VillainInMemDataService)
...

This is a switch that ensures the in-memory-web-api will be used in non-production environments.

Recap

This post has introduced the in-memory-web-api which greatly simplifies spinning up Angular prototypes, proof of concepts or learning aids that require a place to store data.

I have then walked through a number of supporting services used to work with the in-memory-web-api.

At this point there is no front end or anything tangible that you can use. This will be added in the upcoming posts where each CRUD operation will be discussed in turn.

Where to go from here?

All the code from this post can be found on GitHub.

Read from the CRUD paradigm will be the focus of the next post.

Acknowledgements

The in-memory-web-api home page on GitHub. My examples here are based on the sample code available within the project.

2 thoughts on “Angular CRUD 1: in-memory-web-api installation and configuration

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.