MEAN Stack Angular 6 CRUD Web Application

by Didin J. on May 20, 2018 MEAN Stack Angular 6 CRUD Web Application

Step by step tutorial on building MEAN Stack (Angular 6) Create-Read-Update-Delete (CRUD) Web Application

The comprehensive step by step tutorial on building MEAN Stack (Angular 6) Create-Read-Update-Delete (CRUD) Web Application. In this MEAN Stack Angular 6 tutorial we will be focused in the new feature in Angular 6 that support CRUD operation. Start from RESTful API access to UI/UX using Angular 6 Material, it different than previous MEAN Stack tutorial.


Table of Contents:


The following tools, frameworks, and modules are required for this tutorial:

- Node.js (recommended version)
- Angular CLI
- Angular 6
- MongoDB
- Express.js
- Mongoose.js
- Terminal (Mac/Linux) or Node Command Line (Windows)
- IDE or Text Editor

We assume that you have installed Node.js. Now, we need to check the Node.js and NPM versions. Open the terminal or Node command line then type this commands.

node -v
v8.11.1
npm -v
6.0.0

That's the Node.js and NPM version that we are using. Now, you can go to the main steps.


1. MEAN Stack Angular 6: Install Angular CLI and Create Angular 6 Web Application

To install or upgrade the latest Angular 6 CLI, type this command in the terminal or Node command line.

sudo npm install -g @angular/cli

If you use windows, it might be not necessary to add `sudo`. Next, create a new Angular 6 Web Application using this Angular CLI command.

ng new mean-angular6

Next, go to the newly created Angular 6 project folder.

cd ./mean-angular6

Now, run the new Angular 6 web application using this command.

ng serve

Open the browser then go to this address `localhost:4200` and you will see this page.

MEAN Stack Angular 6 CRUD Web Application - Angular Home


2. MEAN Stack Angular 6: Install and Configure Express.js

Close the running Angular 6 app first by press `ctrl+c` then type this command for adding Express.js modules and its dependencies.

npm install --save express body-parser morgan body-parser serve-favicon http-errors

Then, add bin folder and a `www` file inside the `bin` folder.

mkdir bin
touch bin/www

Open and edit www file then add this lines of codes.

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('mean-angular6:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

To make the server run from `bin/www`, open and edit `package.json` then replace `start` value.

"scripts": {
  "ng": "ng",
  "start": "ng build && node ./bin/www",
  "build": "ng build",
  "test": "ng test",
  "lint": "ng lint",
  "e2e": "ng e2e"
},

Now, create `app.js` in the root of the project folder.

touch app.js

Open and edit `app.js` then add all these lines of codes.

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');

var apiRouter = require('./routes/book');

var app = express();

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'dist/mean-angular6')));
app.use('/', express.static(path.join(__dirname, 'dist/mean-angular6')));
app.use('/api', apiRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.send(err.status);
});

module.exports = app;

Next, create routes folder then create routes file for the book.

mkdir routes
touch routes/book.js

Open and edit `routes/book.js` file then add this lines of codes.

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.send('Express RESTful API');
});

module.exports = router;

Now, run the server using this command.

npm start

You will see the previous Angular landing page when you point your browser to `http://localhost:3000`. When you change the address to `http://localhost:3000/api` you will see this page.

MEAN Stack Angular 6 CRUD Web Application - Express RESTful API

Now, we have RESTful API with the compiled Angular 6 front end.


3. MEAN Stack Angular 6: Install and Configure Mongoose.js

We need to access data from MongoDB. For that, we will install and configure `Mongoose.js`. On the terminal type this command after stopping the running Express server.

npm install --save mongoose

Open and edit `app.js` then add this lines after another variable line.

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/mean-angular6', { promiseLibrary: require('bluebird') })
  .then(() =>  console.log('connection successful'))
  .catch((err) => console.error(err));

Now, run MongoDB server on different terminal tab or command line or run from the service.

mongod

Next, you can test the connection to MongoDB run again the Node application and you will see this message on the terminal.

connection successful


4. MEAN Stack Angular 6: Create Mongoose.js Model

Add a models folder on the root of the project folder for hold `Mongoose.js` model files.

mkdir models

Create a new Javascript file that uses for `Mongoose.js` model. We will create a model of Book collection.

touch models/Book.js

Now, open and edit that file and add Mongoose require.

var mongoose = require('mongoose');

Then add model fields like this.

var BookSchema = new mongoose.Schema({
  isbn: String,
  title: String,
  author: String,
  description: String,
  published_year: String,
  publisher: String,
  updated_date: { type: Date, default: Date.now },
});

That Schema will mapping to MongoDB collections called book. If you want to know more about Mongoose Schema Datatypes you can find it here. Next, export that schema.

module.exports = mongoose.model('Book', BookSchema);


5. MEAN Stack Angular 6: Create Routes for Accessing Book Data via Restful API

Open and edit again `routes/book.js` then replace all codes with this.

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Book = require('../models/Book.js');

/* GET ALL BOOKS */
router.get('/', function(req, res, next) {
  Book.find(function (err, products) {
    if (err) return next(err);
    res.json(products);
  });
});

/* GET SINGLE BOOK BY ID */
router.get('/:id', function(req, res, next) {
  Book.findById(req.params.id, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* SAVE BOOK */
router.post('/', function(req, res, next) {
  Book.create(req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* UPDATE BOOK */
router.put('/:id', function(req, res, next) {
  Book.findByIdAndUpdate(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

/* DELETE BOOK */
router.delete('/:id', function(req, res, next) {
  Book.findByIdAndRemove(req.params.id, req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

module.exports = router;

Run again the Express server then open the other terminal or command line to test the Restful API by type this command.

curl -i -H "Accept: application/json" localhost:3000/api

If that command return response like below then REST API is ready to go.

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 2
ETag: W/"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w"
Date: Fri, 18 May 2018 13:52:20 GMT
Connection: keep-alive

[]

Now, let's populate Book collection with initial data that sent from RESTful API. Run this command to populate it.

curl -i -X POST -H "Content-Type: application/json" -d '{ "isbn":"123442123, 97885654453443","title":"Learn how to build modern web application with MEAN stack","author": "Didin J.","description":"The comprehensive step by step tutorial on how to build MEAN (MongoDB, Express.js, Angular 5 and Node.js) stack web application from scratch","published_year":"2017","publisher":"Djamware.com" }' localhost:3000/api

You will see this response to the terminal if success.

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 415
ETag: W/"19f-OBSCF1PZfqMcJVrpMPdOa/K9GAs"
Date: Fri, 18 May 2018 13:53:17 GMT
Connection: keep-alive

{"_id":"5afedacdafd63f4ad09766c6","isbn":"123442123, 97885654453443","title":"Learn how to build modern web application with MEAN stack","author":"Didin J.","description":"The comprehensive step by step tutorial on how to build MEAN (MongoDB, Express.js, Angular 5 and Node.js) stack web application from scratch","published_year":"2017","publisher":"Djamware.com","updated_date":"2018-05-18T13:53:17.348Z","__v":0}


6. MEAN Stack Angular 6: Create Angular 6 Routes for Navigation between Angular Pages/Component

To create Angular 6 Routes for navigation between Angular 6 pages/component, add or generate all required component.

ng g component book
ng g component book-detail
ng g component book-create
ng g component book-edit

Next, open and edit again `src/app/app.module.ts` then add this import.

import { RouterModule, Routes } from '@angular/router';

Add this lines of codes for Angular routes before `@NgModule`.

const appRoutes: Routes = [
  {
    path: 'books',
    component: BookComponent,
    data: { title: 'Book List' }
  },
  {
    path: 'book-details/:id',
    component: BookDetailComponent,
    data: { title: 'Book Details' }
  },
  {
    path: 'book-create',
    component: BookCreateComponent,
    data: { title: 'Create Book' }
  },
  {
    path: 'book-edit/:id',
    component: BookEditComponent,
    data: { title: 'Edit Book' }
  },
  { path: '',
    redirectTo: '/books',
    pathMatch: 'full'
  }
];

In @NgModule imports, section adds ROUTES constant, so the imports section will be like this.

imports: [
  RouterModule.forRoot(appRoutes),
  BrowserModule,
  FormsModule,
  HttpClientModule
],

To activate that routes in Angular 6, open and edit `src/app/app.component.html` then replace all codes with this.

<router-outlet></router-outlet>


7. MEAN Stack Angular 6: Create Angular 6 Service for Accessing RESTful API

Before creating a service for RESTful API access, first, we have to install or register `HttpClientModule`. Open and edit `src/app/app.module.ts` then add this import.

import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

Add it to `@NgModule` imports after `BrowserModule`.

imports: [
  BrowserModule,
  FormsModule,
  HttpClientModule
],

To create Angular 6 service, simply run this command.

ng g service api

Next, open and edit `src/api.service.ts` then add this imports.

import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { catchError, tap, map } from 'rxjs/operators';

Add these constants before the `@Injectable`.

const httpOptions = {
  headers: new HttpHeaders({'Content-Type': 'application/json'})
};
const apiUrl = "/api";

Inject `HttpClient` module to the constructor.

constructor(private http: HttpClient) { }

Add the error handler function.

private handleError(error: HttpErrorResponse) {
  if (error.error instanceof ErrorEvent) {
    // A client-side or network error occurred. Handle it accordingly.
    console.error('An error occurred:', error.error.message);
  } else {
    // The backend returned an unsuccessful response code.
    // The response body may contain clues as to what went wrong,
    console.error(
      `Backend returned code ${error.status}, ` +
      `body was: ${error.error}`);
  }
  // return an observable with a user-facing error message
  return throwError('Something bad happened; please try again later.');
};

Add a function for extract response data.

private extractData(res: Response) {
  let body = res;
  return body || { };
}

Add all CRUD (create, read, update, delete) functions of books data.

getBooks(): Observable<any> {
  return this.http.get(apiUrl, httpOptions).pipe(
    map(this.extractData),
    catchError(this.handleError));
}

getBook(id: string): Observable<any> {
  const url = `${apiUrl}/${id}`;
  return this.http.get(url, httpOptions).pipe(
    map(this.extractData),
    catchError(this.handleError));
}

postBook(data): Observable<any> {
  return this.http.post(apiUrl, data, httpOptions)
    .pipe(
      catchError(this.handleError)
    );
}

updateBook(data): Observable<any> {
  return this.http.put(apiUrl, data, httpOptions)
    .pipe(
      catchError(this.handleError)
    );
}

deleteBook(id: string): Observable<{}> {
  const url = `${apiUrl}/${id}`;
  return this.http.delete(url, httpOptions)
    .pipe(
      catchError(this.handleError)
    );
}


8. MEAN Stack Angular 6: Display Book List using Angular 6 Component and Material

We will display a book list that gets via API Service. For that, open and edit `src/app/book/book.component.ts` then add this imports.

import { ApiService } from '../api.service';

Next, inject the API Service to the constructor.

constructor(private api: ApiService) { }

Add a variable for hold books list before the constructor.

books: any;

Modify the `ngOnInit` function to get book list immediately.

ngOnInit() {
  this.api.getBooks()
    .subscribe(res => {
      console.log(res);
      this.books = res;
    }, err => {
      console.log(err);
    });
}

Next, for user interface (UI) we will use Angular 6 Material. There's a new feature for generating a Material component like Table as a component, but we will create or add the Table component from scratch to existing component. Type this command to install Angular Material.

ng add @angular/material

We will register all required Angular Material components or modules to `app.module.ts`. Open and edit that file then add this imports.

import {
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule } from "@angular/material";

Also, modify `FormsModule` import to add `ReactiveFormsModule`.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to `@NgModule` imports.

imports: [
  RouterModule.forRoot(appRoutes),
  BrowserModule,
  FormsModule,
  ReactiveFormsModule,
  HttpClientModule,
  BrowserAnimationsModule,
  MatInputModule,
  MatTableModule,
  MatPaginatorModule,
  MatSortModule,
  MatProgressSpinnerModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],

Next, back to `src/book/book.component.ts` then add this imports.

import { DataSource } from '@angular/cdk/collections';
import { Observable } from 'rxjs';

Declare the variables of Angular Material Table Data Source after the books variable.

displayedColumns = ['isbn', 'title', 'author'];
dataSource = new BookDataSource(this.api);

Add a class for the Book Data Source.

export class BookDataSource extends DataSource<any> {
  constructor(private api: ApiService) {
    super()
  }

  connect() {
    return this.api.getBooks();
  }

  disconnect() {

  }
}

Next, open and edit `src/book/book.component.html` then replace all HTML tags with this Angular Material tags.

<div class="button-row">
  <a mat-raised-button color="primary" [routerLink]="['/book-create']"><mat-icon>add</mat-icon></a>
</div>
<div class="example-container mat-elevation-z8">
  <table mat-table #table [dataSource]="dataSource">

    <!--- Note that these columns can be defined in any order.
          The actual rendered columns are set as a property on the row definition" -->

    <!-- Title Column -->
    <ng-container matColumnDef="isbn">
      <th mat-header-cell *matHeaderCellDef> ISBN </th>
      <td mat-cell *matCellDef="let element" class="isbn-col"> {{element.isbn}} </td>
    </ng-container>

    <!-- Title Column -->
    <ng-container matColumnDef="title">
      <th mat-header-cell *matHeaderCellDef> Title </th>
      <td mat-cell *matCellDef="let element"> {{element.title}} </td>
    </ng-container>

    <!-- Author Column -->
    <ng-container matColumnDef="author">
      <th mat-header-cell *matHeaderCellDef> Author </th>
      <td mat-cell *matCellDef="let element"> {{element.author}} </td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/book-details/', row._id]"></tr>
  </table>
</div>

Finally, to make a little UI adjustment, open and edit `src/book/book.component.css` then add this CSS codes.

.example-container {
  display: flex;
  flex-direction: column;
  max-height: 500px;
  min-width: 300px;
  overflow: auto;
}

.isbn-col {
  flex: 0 0 100px !important;
  white-space: unset !important;
}

.button-row {
  margin: 10px 0;
}


9. MEAN Stack Angular 6: Show Book Details using Angular 6 Component and Material

To show book details after click or tap on the one of a row inside the Angular Material table, open and edit `src/book-detail/book-detail.component.ts` then add this imports.

import { ActivatedRoute } from '@angular/router';
import { ApiService } from '../api.service';

Inject above modules to the constructor.

constructor(private route: ActivatedRoute, private api: ApiService) { }

Declare a variable before the constructor for hold book data that get from the API.

book = {};

Add a function for getting Book data from the API.

getBookDetails(id) {
  this.api.getBook(id)
    .subscribe(data => {
      console.log(data);
      this.book = data;
    });
}

Call that function when the component is initiated.

ngOnInit() {
  this.getBookDetails(this.route.snapshot.params['id']);
}

For the view, open and edit `src/book-detail/book-detail.component.html` then replace all HTML tags with this.

<div class="button-row">
  <a mat-raised-button color="primary" [routerLink]="['/books']"><mat-icon>list</mat-icon></a>
</div>
<mat-card class="example-card">
  <mat-card-header>
    <mat-card-title><h2>{{book.title}}</h2></mat-card-title>
    <mat-card-subtitle>{{book.description}}</mat-card-subtitle>
  </mat-card-header>
  <mat-card-content>
    <dl>
      <dt>ISBN:</dt>
      <dd>{{book.isbn}}</dd>
      <dt>Author:</dt>
      <dd>{{book.author}}</dd>
      <dt>Publisher:</dt>
      <dd>{{book.publisher}}</dd>
      <dt>Publish Year:</dt>
      <dd>{{book.published_year}}</dd>
      <dt>Update Date:</dt>
      <dd>{{book.updated_date | date}}</dd>
    </dl>
  </mat-card-content>
  <mat-card-actions>
    <a mat-raised-button color="primary" [routerLink]="['/book-edit', book._id]"><mat-icon>edit</mat-icon></a>
    <a mat-raised-button color="warn" (click)="deleteBook(book._id)"><mat-icon>delete</mat-icon></a>
  </mat-card-actions>
</mat-card>

Finally, open and edit `src/book-detail/book-detail.component.css` then add this lines of CSS codes.

.example-card {
  max-width: 500px;
}

.button-row {
  margin: 10px 0;
}


10. MEAN Stack Angular 6: Add a Book using Angular 6 Component and Material

To create a form for adding a Book, open and edit `src/book-create/book-create.component.ts` then add this imports.

import { Router } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';

Inject above modules to the constructor.

constructor(private router: Router, private api: ApiService, private formBuilder: FormBuilder) { }

Declare variables for the Form Group and all of the required fields inside the form before the constructor.

bookForm: FormGroup;
isbn:string='';
title:string='';
description:string='';
author:string='';
publisher:string='';
published_year:string='';

Add initial validation for each field.

ngOnInit() {
  this.bookForm = this.formBuilder.group({
    'isbn' : [null, Validators.required],
    'title' : [null, Validators.required],
    'description' : [null, Validators.required],
    'author' : [null, Validators.required],
    'publisher' : [null, Validators.required],
    'published_year' : [null, Validators.required]
  });
}

Create a function for submitting or POST book form.

onFormSubmit(form:NgForm) {
  this.api.postBook(form)
    .subscribe(res => {
        let id = res['_id'];
        this.router.navigate(['/book-details', id]);
      }, (err) => {
        console.log(err);
      });
}

Next, open and edit `src/book-create/book-create.component.html` then replace all HTML tags with this.

<div class="button-row">
  <a mat-raised-button color="primary" [routerLink]="['/books']"><mat-icon>list</mat-icon></a>
</div>
<form [formGroup]="bookForm" (ngSubmit)="onFormSubmit(bookForm.value)">
  <mat-form-field class="example-full-width">
    <input matInput placeholder="ISBN" formControlName="isbn"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!bookForm.get('isbn').valid && bookForm.get('isbn').touched">Please enter ISBN</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <input matInput placeholder="Title" formControlName="title"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!bookForm.get('title').valid && bookForm.get('title').touched">Please enter Book Title</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <input matInput placeholder="Author" formControlName="author"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!bookForm.get('author').valid && bookForm.get('author').touched">Please enter Book Author</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <textarea matInput placeholder="Description" formControlName="description"
           [errorStateMatcher]="matcher"></textarea>
    <mat-error>
      <span *ngIf="!bookForm.get('description').valid && bookForm.get('description').touched">Please enter Book Description</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <input matInput placeholder="Publisher" formControlName="publisher"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!bookForm.get('publisher').valid && bookForm.get('publisher').touched">Please enter Publisher</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <input matInput placeholder="Published Year" formControlName="published_year"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!bookForm.get('published_year').valid && bookForm.get('published_year').touched">Please enter Published Year</span>
    </mat-error>
  </mat-form-field>
  <div class="button-row">
    <button type="submit" [disabled]="!bookForm.valid" mat-raised-button color="primary"><mat-icon>save</mat-icon></button>
  </div>
</form>

Finally, open and edit `src/book-create/book-create.component.css` then add this CSS codes.

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}


11. MEAN Stack Angular 6: Edit a Book using Angular 6 Component and Material

We have put an edit button inside the Book Detail component. Now, open and edit `src/book-edit/book-edit.component.ts` then add this imports.

import { Router, ActivatedRoute } from '@angular/router';
import { ApiService } from '../api.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';

Inject above modules to the constructor.

constructor(private router: Router, private route: ActivatedRoute, private api: ApiService, private formBuilder: FormBuilder) { }

Declare the Form Group variable and all of the required variables for the book form before the constructor.

bookForm: FormGroup;
id:string = '';
isbn:string = '';
title:string = '';
description:string = '';
author:string = '';
publisher:string = '';
published_year:string = '';

Next, add validation for all fields when the component is initiated.

ngOnInit() {
  this.getBook(this.route.snapshot.params['id']);
  this.bookForm = this.formBuilder.group({
    'isbn' : [null, Validators.required],
    'title' : [null, Validators.required],
    'description' : [null, Validators.required],
    'author' : [null, Validators.required],
    'publisher' : [null, Validators.required],
    'published_year' : [null, Validators.required]
  });
}

Create a function for getting book data that filled to each form fields.

getBook(id) {
  this.api.getBook(id).subscribe(data => {
    this.id = data._id;
    this.bookForm.setValue({
      isbn: data.isbn,
      title: data.title,
      description: data.description,
      author: data.author,
      publisher: data.publisher,
      published_year: data.published_year
    });
  });
}

Create a function to update the book changes.

onFormSubmit(form:NgForm) {
  this.api.updateBook(this.id, form)
    .subscribe(res => {
        let id = res['_id'];
        this.router.navigate(['/book-details', id]);
      }, (err) => {
        console.log(err);
      }
    );
}

Add a function for handling show book details button.

bookDetails() {
  this.router.navigate(['/book-details', this.id]);
}

Next, open and edit `src/book-edit/book-edit.component.html` then replace all HTML tags with this.

<div class="button-row">
  <a mat-raised-button color="primary" (click)="bookDetails()"><mat-icon>show</mat-icon></a>
</div>
<form [formGroup]="bookForm" (ngSubmit)="onFormSubmit(bookForm.value)">
  <mat-form-field class="example-full-width">
    <input matInput placeholder="ISBN" formControlName="isbn"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!bookForm.get('isbn').valid && bookForm.get('isbn').touched">Please enter ISBN</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <input matInput placeholder="Title" formControlName="title"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!bookForm.get('title').valid && bookForm.get('title').touched">Please enter Book Title</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <input matInput placeholder="Author" formControlName="author"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!bookForm.get('author').valid && bookForm.get('author').touched">Please enter Book Author</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <textarea matInput placeholder="Description" formControlName="description"
           [errorStateMatcher]="matcher"></textarea>
    <mat-error>
      <span *ngIf="!bookForm.get('description').valid && bookForm.get('description').touched">Please enter Book Description</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <input matInput placeholder="Publisher" formControlName="publisher"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!bookForm.get('publisher').valid && bookForm.get('publisher').touched">Please enter Publisher</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <input matInput placeholder="Published Year" formControlName="published_year"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!bookForm.get('published_year').valid && bookForm.get('published_year').touched">Please enter Published Year</span>
    </mat-error>
  </mat-form-field>
  <div class="button-row">
    <button type="submit" [disabled]="!bookForm.valid" mat-raised-button color="primary"><mat-icon>save</mat-icon></button>
  </div>
</form>

Finally, open and edit `src/book-edit/book-edit.component.css` then add this lines of CSS codes.

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}


12. MEAN Stack Angular 6: Create a Function for Delete Book inside Book Detail Component

Inside book detail component, you have seen there a button for delete and have click action to a function. Now, open and edit `src/book-detail/book-detail.ts` then modify the `ActivatedRoute` import to add `Router` module.

import { ActivatedRoute, Router } from '@angular/router';

Inject `Router` to the constructor.

constructor(private route: ActivatedRoute, private api: ApiService, private router: Router) { }

Add this function for delete book.

deleteBook(id) {
  this.api.deleteBook(id)
    .subscribe(res => {
        this.router.navigate(['/books']);
      }, (err) => {
        console.log(err);
      }
    );
}


13. MEAN Stack Angular 6: Run and Test the MEAN Stack (Angular 6) CRUD Application

Now, it's a time for testing the MEAN Stack (Angular 6) CRUD Application.

npm start

In the browser go to this URL `localhost:3000` and here the whole application looks like.

MEAN Stack Angular 6 CRUD Web Application - Book List
MEAN Stack Angular 6 CRUD Web Application - Add Book
MEAN Stack Angular 6 CRUD Web Application - Book Details

That it's, we have finished the complete tutorial of MEAN Stack (Angular 6) and Angular Material. If you can't follow the steps of the tutorial, you can compare it with the working source code from our GitHub.

That just the basic. If you need more deep learning about MEAN Stack, Angular, and Node.js, you can find the following books:

For more detailed on MEAN stack and Node.js, you can take the following course:

Thanks!

The following resources might be useful for you:

Loading…