AppDividend
Latest Code Tutorials

Angular 8 Tutorial | Learn Angular 8 CRUD Example

39

In this Angular 8 CRUD Example topic,  You will learn Angular Tutorial with a brief example by building a full-stack CRUD — Create, Read, Update, and Delete web application. We will use Node.js as a platform and Express.js, which is a web framework built on top of Node.js, and MongoDB as a NoSQL Database.

#New Features of Angular 8

The new features of Angular 8 are the following.

  1. TypeScript 3.8 Support.
  2. Web Workers.
  3. Preview of Ivy Rendering Engine.
  4. Dynamic imports for lazy-loaded modules.

You check out the new features in brief on my Angular 8 New Features post.

#What we build in this Angular 8 demo

This tutorial is specially designed for newcomers, and it will help you to up and running with the latest version of Angular, which is right now 8. 

#Prerequisites

  1. You have installed Node.js version > 10. NPM will be in use by default. I am using Node version 11.3.0
  2. You have installed MongoDB on your machine.

#Workflow of Angular 8 CRUD Example

We will create two separate projects. One is for Angular Frontend, and one is for Node.js and Express. That means one for frontend and one for the backend.

We will create the backend API, which deals with storing, modifying, and deleting the form values, and the frontend will consume that API; for example, it shows the data from the backend.

For this example, I am using the following tech stacks with their versions.

  1. Node v11.3.0
  2. NPM v6.9.0
  3. AngularCLI v8.0.1
  4. MongoDB shell version v3.6.3
  5. MongoDB version v3.6.3
  6. Mac OS Mojave

In this blog, I have previously written Angular 7 CRUD and Angular 6 CRUD tutorials in deep. Now, the Angular community has released the next version, which is Angular 8. If you do not know how to upgrade Angular CLI to version 8, then check out my Angular CLI 8 upgrade tutorial. In this MEAN Stack tutorial, we will see Angular Routing, Angular Forms, and on the back end side, we use Node.js and Express.js to handle the data and to store the data, we will use MongoDB.

#Angular 8 Example

In this tutorial, we will use the Angular 8. So if you are not so sure what Angular CLI version you are using, then type the following command, which will tell us the Angular CLI version.

ng --version

Angular 8 Tutorial With Example

#Project Description

We will create a demo project, where a user can save their ProductName, Product Description, and ProductPrice from the Angular form and submit the form to the node.js server. If the values are incorrect, then it will validate at the frontend, and the form will not submit.

We will not validate the field values at the backend because it will make this tutorial more lengthy. If you want to create a validation at the backend, then check out my express form validation tutorial.

If all the values are perfect, then it will send the form values to the Node.js backend API, and it will store the values inside the MongoDB database.

#Step 1: Create An Angular Boilerplate

Let’s create an Angular 8 project using the following command.

Create Angular 8 Project

When you install a brand new Angular project, see the options they are providing while creating a new project. In this project, we need the routing module, so we will allow the Angular CLI to create a routing module for us.

I am using Visual Studio Code as a programming editor to develop Angular application.

Now, go inside the folder and open the project inside the VSCode.

cd angular8tutorial
code .

At the time of installation, we have enabled routing for our angular app. You can check the file called app-routing.module.ts file inside src >> app directory.

Next, install the Bootstrap 4 CSS Framework using the following command. It is not a necessary step, and you can choose your CSS Framework as well.

npm install bootstrap --save

Now, add it inside an angular.json file.

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

It will include a bootstrap CSS file, and we can use the bootstrap classes inside any file.

Start an Angular development server using the following command.

ng serve -o

The server starts at the http://localhost:4200/. You can see the output in the browser. It is an initial Angular home screen.

#Step 2: Generate The Angular Components

Hit the following command to generate the Angular Components. We will perform to create, read, update operations. So we will create three components.

ng g c product-add --skipTests=true
ng g c product-get --skipTests=true
ng g c product-edit --skipTests=true
Option “spec” is deprecated in Angular 8: Use “skipTests” instead.
For this demo, we do not need the tests file. So, we will not create it.
All the three components are automatically registered inside an app.module.ts file. Now, we need to configure the routing of angular components inside an app-routing.module.ts file.

Now, you can see an app-routing.module.ts file inside the src >> app folder file. It is created because while we were installing the angular app, we permitted angular cli to generate the routing file for us.

All the three components are automatically registered inside an app.module.ts file. Now, we need to import and configure the routing of angular components inside the app-routing.module.ts file.

Write the following code inside an app-routing.module.ts file.

// app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ProductAddComponent } from './product-add/product-add.component';
import { ProductEditComponent } from './product-edit/product-edit.component';
import { ProductGetComponent } from './product-get/product-get.component';

const routes: Routes = [
  {
    path: 'product/create',
    component: ProductAddComponent
  },
  {
    path: 'edit/:id',
    component: ProductEditComponent
  },
  {
    path: 'products',
    component: ProductGetComponent
  }
];

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

Now, you can see inside the app.component.html file that <router-outlet> directive is there. This directive helps us to render the different components based on route URI.

#Step 3: Create The Angular Navigation

Write the following code inside the app.component.html file.

<nav class="navbar navbar-expand-sm bg-light">
  <div class="container-fluid">
    <ul class="navbar-nav">
      <li class="nav-item">
        <a routerLink="product/create" class="nav-link" routerLinkActive="active">
          Create Product
        </a>
      </li>
      <li class="nav-item">
        <a routerLink="products" class="nav-link" routerLinkActive="active">
          Products
        </a>
      </li> 
    </ul>
  </div>
</nav>

<div class="container">
  <router-outlet></router-outlet>
</div>

Save the file and go to the browser and click on two links. You can see that we can see the different components based on navigation.

Create an Angular Navigation

#Step 4: Configure Angular Routing Progress Indicator.

Type the following command to install the ng2-slim-loading-bar library.

npm install ng2-slim-loading-bar --save

So, if you install the third-party packages right now, then it is not compatible with an Angular 8. If we want to bridge the gap between Angular 8 and the third-party packages, we need to install the following library. That is it.

npm install rxjs-compat --save

Now, import the SlimLoadingBarModule inside an app.module.ts file.

// app.module.ts

import { SlimLoadingBarModule } from 'ng2-slim-loading-bar';

imports: [
    ...
    SlimLoadingBarModule
],

The next step is, include the styling that comes with the library inside src  >>  styles.css file.

@import "../node_modules/ng2-slim-loading-bar/style.css";

#Step 5: Add Router Events.

Angular RouterModule gives us the following event modules.

  1. NavigationStart
  2. NavigationEnd
  3. NavigationError
  4. NavigationCancel
  5. Router
  6. Event

Now, write the following code inside the  app.component.ts file.

// app.component.ts

import { Component } from '@angular/core';
import {SlimLoadingBarService} from 'ng2-slim-loading-bar';
import { NavigationCancel,
        Event,
        NavigationEnd,
        NavigationError,
        NavigationStart,
        Router } from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'angular8tutorial';
  constructor(private loadingBar: SlimLoadingBarService, private router: Router) {
    this.router.events.subscribe((event: Event) => {
      this.navigationInterceptor(event);
    });
  }
  private navigationInterceptor(event: Event): void {
    if (event instanceof NavigationStart) {
      this.loadingBar.start();
    }
    if (event instanceof NavigationEnd) {
      this.loadingBar.complete();
    }
    if (event instanceof NavigationCancel) {
      this.loadingBar.stop();
    }
    if (event instanceof NavigationError) {
      this.loadingBar.stop();
    }
  }
}

In the above code, we have told the Angular application that while we navigate from one component to another component, while we want to display the router progress indicator. 

When a user clicks the other route, the angular progress indicator start showing, and when the navigation is complete, it will stop displaying. So, it is a kind of best User experience for the user.

What it is doing in the code that it intercepts the routing event and add the loading bar component to the loading route so that we can see the routing indication every time we change the routes.

The final change is to display the routing indicator is that we need to insert an ng2-slim-loading-bar directive inside the app.component.html file at the top of the page.

<!-- app.component.html -->

<ng2-slim-loading-bar color="blue"></ng2-slim-loading-bar>
<nav class="navbar navbar-expand-sm bg-light">
  <div class="container-fluid">
    <ul class="navbar-nav">
      <li class="nav-item">
        <a routerLink="product/create" class="nav-link" routerLinkActive="active">
          Create Product
        </a>
      </li>
      <li class="nav-item">
        <a routerLink="products" class="nav-link" routerLinkActive="active">
          Products
        </a>
      </li> 
    </ul>
  </div>
</nav>

<div class="container">
  <router-outlet></router-outlet>
</div>

Save the file and go to the terminal to see if there any error, and if there is no error, then go to the browser and change the routes, and you can see that now we can see the routing indicator while changing the different angular routes.

#Step 6: Add The Bootstrap To HTML Form

Inside the product-add.component.html file, add the following bootstrap 4 form. We have three HTML fields for this demo.

<!-- product-add.component.html -->

<div class="card">
  <div class="card-body">
    <form>
      <div class="form-group">
        <label class="col-md-4">Product Name</label>
        <input type="text" class="form-control" />
      </div>
      <div class="form-group">
        <label class="col-md-4">Product Description </label>
        <textarea class="form-control" rows = 7 cols = "5"></textarea>
      </div>
      <div class="form-group">
        <label class="col-md-4">Product Price</label>
        <input type="text" class="form-control" />
      </div>
      <div class="form-group">
        <button type="submit" class="btn btn-primary">Create Product</button>
      </div>
    </form>
  </div>
</div>

Add Bootstrap Form

#Step 7: Create Angular 8 Form Validation

For form validation, we have two options. Template-based and Reactive Forms Module. We will use the ReactiveFormsModule approach. Now, if you are new to Angular Form Validation, then please check out my this article Angular Form Validation Example Tutorial on this blog.

Now, import the ReactiveFormsModule inside the app.module.ts file. It comes with an Angular project by default.

// app.module.ts

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

imports: [
    ...
    ReactiveFormsModule
],

Now, we need to write a code for the app.component.ts file. Remember, this is not the template-driven form. So we need to add the code inside the app.component.ts file.

First, we import the FormGroup, FormBuilder, Validators modules from @angular/forms.

Also, create a constructor and instantiate the FormBuilder.

So write the following code inside the product-add.component.ts file.

// product-add.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup,  FormBuilder,  Validators } from '@angular/forms';

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

  angForm: FormGroup;
  constructor(private fb: FormBuilder) {
    this.createForm();
  }

  createForm() {
    this.angForm = this.fb.group({
      ProductName: ['', Validators.required ],
      ProductDescription: ['', Validators.required ],
      ProductPrice: ['', Validators.required ]
    });
  }

  ngOnInit() {
  }

}

We have used a form builder to handle all the validation. So in that constructor, we are creating the form with all the validation rules for particular fields. In our example, there are three fields. If the input text is empty, then it will give an error, and we need to display that error.

Now, write the following code inside the product-add.component.html file.

<!-- product-add.component.html -->

<div class="card">
  <div class="card-body">
    <form [formGroup]="angForm" novalidate>
      <div class="form-group">
        <label class="col-md-4">Product Name</label>
        <input type="text" class="form-control" 
          formControlName="ProductName" 
          #ProductName />
      </div>
      <div *ngIf="angForm.controls['ProductName'].invalid && (angForm.controls['ProductName'].dirty || angForm.controls['ProductName'].touched)" class="alert alert-danger">
        <div *ngIf="angForm.controls['ProductName'].errors.required">
          Product Name is required.
        </div>
      </div>
      <div class="form-group">
        <label class="col-md-4">Product Description </label>
        <textarea class="form-control" rows = 7 cols = "5"
        formControlName="ProductDescription" 
        #ProductDescription></textarea>
      </div>
      <div *ngIf="angForm.controls['ProductDescription'].invalid && (angForm.controls['ProductDescription'].dirty || angForm.controls['ProductDescription'].touched)" class="alert alert-danger">
        <div *ngIf="angForm.controls['ProductDescription'].errors.required">
          Product Description is required.
        </div>
      </div>
      <div class="form-group">
        <label class="col-md-4">Product Price</label>
        <input type="text" class="form-control" 
          formControlName="ProductPrice" 
          #ProductPrice
        />
      </div>
      <div *ngIf="angForm.controls['ProductPrice'].invalid && (angForm.controls['ProductPrice'].dirty || angForm.controls['ProductPrice'].touched)" class="alert alert-danger">
        <div *ngIf="angForm.controls['ProductPrice'].errors.required">
          Product Price is required.
        </div>
      </div>
      <div class="form-group">
        <button type="submit" class="btn btn-primary"
        [disabled]="angForm.pristine || angForm.invalid" >
          Create Product
        </button>
      </div>
    </form>
  </div>
</div>

Save the file and go to the browser, and you can see if you do not put any value inside the input box, then you will see the errors.

Add Angular 8 Form Validation

#Step 8: Add And Configure the HttpClientModule

Most front-end applications communicate with the backend services over an HTTP protocol. Modern browsers support the two different APIs for making HTTP requests: the XMLHttpRequest interface and the fetch API.

The HttpClient in @angular/common/http offers a simplified client HTTP API for Angular applications that rests on the XMLHttpRequest interface exposed by browsers. Additional benefits of the HttpClient include the testability features, typed request and response objects, request and response interception, the Observables APIs, and smooth error handling.

Import the HttpClientModule inside an app.module.ts file.

// app.module.ts

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

imports: [
   ...
    HttpClientModule
 ],

#Step 9: Create A TypeScript Model file.

Inside the src >> app folder, create one file called Product.ts and add the following code.

// Product.ts

export default class Product {
  ProductName: string;
  ProductDescription: string;
  ProductPrice: number;
}

#Step 10: Create An Angular Service file.

Type the following command to generate the service file.

The primary use of the service file is that we are adding all of our AJAX code inside that file. So, it sends an ajax request to the backend server and retrieves the data from the backend server.

ng g service products --skipTests=true

So, your necessary products.service.ts file looks like this.

// products.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ProductsService {

  constructor() { }
}

Now, import the products.service.ts file into the app.module.ts file.

// app.module.ts

import { ProductsService } from './products.service';

providers: [ ProductsService ],

#Step 11: Submit Form Values To Node Server

Now, we need to write the code that will send the HTTP POST request with the data to the Node.js server and store the data into the MongoDB database.

Write the following code inside the products.service.ts file.

// products.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class ProductsService {

  uri = 'http://localhost:4000/products';

  constructor(private http: HttpClient) { }

  addProduct(ProductName, ProductDescription, ProductPrice) {
    const obj = {
      ProductName,
      ProductDescription,
      ProductPrice
    };
    console.log(obj);
    this.http.post(`${this.uri}/add`, obj)
        .subscribe(res => console.log('Done'));
  }
}

We have defined our backend API URL, but we have not created any backend yet, but we will do it in the following couple of steps.

Now, we need to add the click event to the Add Product Button. So add the following code inside a product-add.component.html file.

<div class="form-group">
        <button (click) = "addProduct(ProductName.value, ProductDescription.value, ProductPrice.value)" type="submit" class="btn btn-primary"
        [disabled]="angForm.pristine || angForm.invalid" >
          Create Product
        </button>
</div>

So when there are no errors, we can submit the form, and it will call the component’s addPropduct function. From there, we will call the angular service, and the service will send the HTTP Post request to the Node.js server.

Now, add the addProduct function inside the product-add.component.ts file. So write the following code inside the product-add.component.ts file.

// product-add.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { ProductsService } from '../products.service';

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

  angForm: FormGroup;
  constructor(private fb: FormBuilder, private ps: ProductsService) {
    this.createForm();
  }

  createForm() {
    this.angForm = this.fb.group({
      ProductName: ['', Validators.required ],
      ProductDescription: ['', Validators.required ],
      ProductPrice: ['', Validators.required ]
    });
  }

  addProduct(ProductName, ProductDescription, ProductPrice) {
    this.ps.addProduct(ProductName, ProductDescription, ProductPrice);
  }

  ngOnInit() {
  }

}

In the above code, first, I have imported the products.service.ts file. Then we have defined the addProduct method.

This method is called when the user clicks on the create product button, and the flow will transfer over here. We get the ProductName, ProductDescription, and ProductPrice data inside the addProduct function.

Inside that function, we are calling the products.service.ts file’s addProduct function and pass the three parameters with it.

So, from the products.service.ts file will call the API and save the products on the server. Now, we need to configure the backend API.

#Step 12: Create A Backend API In Node.js

Inside the angular root folder, create one folder called the API and go inside that folder. Remember, it will be a completely separate project from the Angular frontend project. So its node_modules folders are different from the Angular project.

Open a terminal inside the API folder and type the following command. It will generate the package.json file using NPM. We do not want to specify each option one by one; that is why we are typing the following command.

npm init -y

Install the following node-specific modules.

npm install express body-parser cors mongoose --save

I do not want to restart the node server each time; I change the file. So I am installing the nodemon server. What it does is that, when I modify a server.js file, it restarts a node.js server automatically.

npm install nodemon --save-dev

We are also installing the body-parser module to parse the data from the incoming Http request.

We have installed the CORS module because our both angular and node application is running on the different ports.

The browser will not allow the CROSS REQUEST ORIGIN attack; that is why we need to install the CORS module to receive the proper request at the backend server.

We have installed the Mongoose module because it provides the ORM for the MongoDB database.

Now, inside the API folder, create one file called server.js file. Add the following code inside that file.

// server.js

const express = require('express'),
    path = require('path'),
    bodyParser = require('body-parser'),
    cors = require('cors'),
    mongoose = require('mongoose');

    const app = express();
    let port = process.env.PORT || 4000;

    const server = app.listen(function(){
        console.log('Listening on port ' + port);
    });

The next thing is to connect the MongoDB database with our node express application.

If you have not installed a MongoDB database, then install it and then start the MongoDB server using the following command.

mongod

So, Now, I have connected to a database.

Create one file called DB.js inside the api root project folder. Write the following code inside a DB.js file.

// DB.js

module.exports = {
  DB: 'mongodb://localhost:27017/ng8crud'
};

Import this DB.js file inside our server.js file and use the Mongoose library to set up the database connection with MongoDB. We can also use the Mongoose to save the data in the database using the Mongoose ORM.

Write the following code inside a server.js file to connect our MongoDB application to a Node.js server.

// server.js

const express = require('express'),
    path = require('path'),
    bodyParser = require('body-parser'),
    cors = require('cors'),
    mongoose = require('mongoose'),
    config = require('./DB');

    mongoose.Promise = global.Promise;
    mongoose.connect(config.DB, { useNewUrlParser: true }).then(
      () => {console.log('Database is connected') },
      err => { console.log('Can not connect to the database'+ err)}
    );

    const app = express();
    app.use(bodyParser.json());
    app.use(cors());
    const port = process.env.PORT || 4000;

    const server = app.listen(port, function(){
     console.log('Listening on port ' + port);
    });

Save the above server.js file and go to the terminal and start the node server using the following command. Remember, we are using nodemon.

nodemon server

So, right now, you have three servers running.

  1. Angular Development Server for frontend.
  2. Nodemon server for the backend.
  3. MongoDB server for database.

Remember, all three servers are running fine without any error; otherwise, our application will not work, and it will crash.

#Step 13: Create Route And Model Files.

Now, we need to create two folders inside the api root folder called routes and models.

In the models’ folder, create one model called Product.js.

// Product.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

// Define collection and schema for Product
let Product = new Schema({
  ProductName: {
    type: String
  },
  ProductDescription: {
    type: String
  },
  ProductPrice: {
    type: Number
  }
},{
    collection: 'Product'
});

module.exports = mongoose.model('Product', Product);

So, we have defined our schema for the Product collection. We have three fields called ProductName, ProductDescription, ProductPrice.

In the routes folder, create one file called the product.route.js.

Write the CRUD code inside the product.route.js file.

// product.route.js

const express = require('express');
const app = express();
const productRoutes = express.Router();

// Require Product model in our routes module
let Product = require('../models/Product');

// Defined store route
productRoutes.route('/add').post(function (req, res) {
  let product = new Product(req.body);
  product.save()
    .then(product => {
      res.status(200).json({'Product': 'Product has been added successfully'});
    })
    .catch(err => {
    res.status(400).send("unable to save to database");
    });
});

// Defined get data(index or listing) route
productRoutes.route('/').get(function (req, res) {
  Product.find(function (err, products){
    if(err){
      console.log(err);
    }
    else {
      res.json(products);
    }
  });
});

// Defined edit route
productRoutes.route('/edit/:id').get(function (req, res) {
  let id = req.params.id;
  Product.findById(id, function (err, product){
      res.json(product);
  });
});

//  Defined update route
productRoutes.route('/update/:id').post(function (req, res) {
  Product.findById(req.params.id, function(err, product) {
    if (!product)
      res.status(404).send("Record not found");
    else {
      product.ProductName = req.body.ProductName;
      product.ProductDescription = req.body.ProductDescription;
      product.ProductPrice = req.body.ProductPrice;

      product.save().then(product => {
          res.json('Update complete');
      })
      .catch(err => {
            res.status(400).send("unable to update the database");
      });
    }
  });
});

// Defined delete | remove | destroy route
productRoutes.route('/delete/:id').get(function (req, res) {
    Product.findByIdAndRemove({_id: req.params.id}, function(err, product){
        if(err) res.json(err);
        else res.json('Successfully removed');
    });
});

module.exports = productRoutes;

Here, we have used the mongoose model to save, update, delete the data from the database. Mongoose is an ORM used in the MongoDB database. It will handle all the CRUD task at the backend. Now, we have all the CRUD operations functions set up on the route file. The final thing is that we need to import inside the server.js file.

Remember, the server.js file is the starting point of our backend node application. So every module needs to be included inside the server.js file.

So, our final server.js file looks like this.

// server.js

const express = require('express'),
    path = require('path'),
    bodyParser = require('body-parser'),
    cors = require('cors'),
    mongoose = require('mongoose'),
    config = require('./DB');

   const productRoute = require('./routes/product.route');
    mongoose.Promise = global.Promise;
    mongoose.connect(config.DB, { useNewUrlParser: true }).then(
      () => {console.log('Database is connected') },
      err => { console.log('Can not connect to the database'+ err)}
    );

    const app = express();
    app.use(bodyParser.json());
    app.use(cors());
    app.use('/products', productRoute);
    const port = process.env.PORT || 4000;

    const server = app.listen(port, function(){
     console.log('Listening on port ' + port);
    });

Now, go to the terminal and start the node server if you have not already started.

node server

#Step 14: Save Data In MongoDB Database

If all of your servers are up and running, then you can go to the browser and fill the form data and add the Product. You can see something like this on your screen if you are successful.

Sometimes, if you are running an adblocker on the browser, then it will not work. So please turn off the adblocker and try this example again.

Test the store data functionality

Now, we can check on the database using the following commands.

First, open the mongo shell on the 4th tab because all the other three tabs are occupied at the moment.

mongo

MongoDB Add

Here, we can see that the values are storing in the MongoDB database. Yess!! We have succeeded.

Now, remaining operations are Read, Update, and Delete.

#Step 15: Show The Data On Angular Frontend

We can iterate the backend data and display the data in the tabular format using Angular ngfor directive.

Inside the product-get.component.html file, write the following code.

<!-- product-get.component.html -->

<table class="table table-hover">
  <thead>
  <tr>
      <td>Product Name</td>
      <td>Product Description</td>
      <td>Product Price</td>
      <td colspan="2">Actions</td>
  </tr>
  </thead>

  <tbody>
      <tr *ngFor="let product of products">
          <td>{{ product.ProductName }}</td>
          <td>{{ product.ProductDescription }}</td>
          <td>{{ product.ProductPrice }}</td>
          <td><a [routerLink]="['/edit', product._id]" class="btn btn-primary">Edit</a></td>
          <td><a [routerLink]="" class="btn btn-danger">Delete</a></td>
      </tr>
  </tbody>
</table>

Now, inside the products.service.ts file, we need to write the function that fetches the product data from the MongoDB database and display it at the Angular application.

// products.service.ts

getProducts() {
    return this
           .http
           .get(`${this.uri}`);
  }

In the above getProducts() function, we have sent the HTTP GET request to the Node.js server and fetch the data from the database.

Now, we need to include the products.service.ts file and Product.ts file inside a product-get.component.ts file.

Write the following code inside the product-get.component.ts file.

// product-get.component.ts

import { Component, OnInit } from '@angular/core';
import Product from '../Product';
import { ProductsService } from '../products.service';

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

  products: Product[];
  constructor(private ps: ProductsService) { }

  ngOnInit() {
    this.ps
      .getProducts()
      .subscribe((data: Product[]) => {
        this.products = data;
    });
  }

}

Save the file and go to the browser and switch to this URL: http://localhost:4200/products. You can see the listing of the products.

Display the data on the frontend

 

#Step 16: Edit And Update Fields

Okay, first, we need to fetch the data from the MongoDB database using _id wise and display that data in the product-edit.component.html file.

For that, when the product-edit.component.html loads, we send an AJAX request to the node server and fetch the particular row using the _id and display the data to their respective fields inside the HTML form.

So first, write the following code inside the product-edit.component.ts file.

// product-edit.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ProductsService } from '../products.service';

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

  angForm: FormGroup;
  product: any = {};

  constructor(private route: ActivatedRoute, private router: Router, private ps: ProductsService, private fb: FormBuilder) {
      this.createForm();
 }

  createForm() {
    this.angForm = this.fb.group({
      ProductName: ['', Validators.required ],
      ProductDescription: ['', Validators.required ],
      ProductPrice: ['', Validators.required ]
    });
  }

  ngOnInit() {
    this.route.params.subscribe(params => {
        this.ps.editProduct(params['id']).subscribe(res => {
          this.product = res;
      });
    });
  }
}

Here, when the product-edit component.ts render, it will call the ngOnInit method and send an HTTP request to the node server and fetch the data from an _id to display inside the product-edit component.html file.

Now, inside the products.service.ts file, we need to code the editProduct function to send an HTTP request.

// products.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class ProductsService {

  uri = 'http://localhost:4000/products';

  constructor(private http: HttpClient) { }

  addProduct(ProductName, ProductDescription, ProductPrice) {
    console.log(ProductName, ProductDescription, ProductPrice);
    const obj = {
      ProductName,
      ProductDescription,
      ProductPrice
    };
    this.http.post(`${this.uri}/add`, obj)
        .subscribe(res => console.log('Done'));
  }

  getProducts() {
    return this
           .http
           .get(`${this.uri}`);
  }

  editProduct(id) {
    return this
            .http
            .get(`${this.uri}/edit/${id}`);
    }
}

Now, finally, we need to write the form inside the product-edit.component.html file.

<!-- product-edit.component.html -->

<div class="card">
  <div class="card-body">
    <form [formGroup]="angForm" novalidate>
      <div class="form-group">
        <label class="col-md-4">Product Name</label>
        <input type="text" class="form-control" 
          formControlName="ProductName" 
          #ProductName 
          [(ngModel)] = "product.ProductName"/>
      </div>
      <div *ngIf="angForm.controls['ProductName'].invalid && (angForm.controls['ProductName'].dirty || angForm.controls['ProductName'].touched)" class="alert alert-danger">
        <div *ngIf="angForm.controls['ProductName'].errors.required">
          Product Name is required.
        </div>
      </div>
      <div class="form-group">
        <label class="col-md-4">Product Description </label>
        <textarea class="form-control" rows = 7 cols = "5"
        formControlName="ProductDescription" 
        #ProductDescription [(ngModel)] = "product.ProductDescription"></textarea>
      </div>
      <div *ngIf="angForm.controls['ProductDescription'].invalid && (angForm.controls['ProductDescription'].dirty || angForm.controls['ProductDescription'].touched)" class="alert alert-danger">
        <div *ngIf="angForm.controls['ProductDescription'].errors.required">
          Product Description is required.
        </div>
      </div>
      <div class="form-group">
        <label class="col-md-4">Product Price</label>
        <input type="text" class="form-control" 
          formControlName="ProductPrice" 
          #ProductPrice
          [(ngModel)] = "product.ProductPrice"
        />
      </div>
      <div *ngIf="angForm.controls['ProductPrice'].invalid && (angForm.controls['ProductPrice'].dirty || angForm.controls['ProductPrice'].touched)" class="alert alert-danger">
        <div *ngIf="angForm.controls['ProductPrice'].errors.required">
          Product Price is required.
        </div>
      </div>
      <div class="form-group">
        <button (click) = "updateProduct(ProductName.value, ProductDescription.value, ProductPrice.value)" type="submit" class="btn btn-primary"
        [disabled]="angForm.invalid" >
          Update Product
        </button>
      </div>
    </form>
  </div>
</div>

Save the file and go to the listing page and click on the edit button and you will see the populated form fields from the database.

You can also see the warning like the following. Ignore this demo tutorial.

forms.js:1193
It looks like you’re using ngModel on the same form field as formControlName.
Support for using the ngModel input property and ngModelChange event with
reactive form directives have been deprecated in Angular v6 and will be removed
in Angular v7.
Now, we need to update the data into the database.
Inside the products.service.ts file, we need to write a function that updates the form fields data. See the following code.
// products.service.ts

updateProduct(ProductName, ProductDescription, ProductPrice, id) {
    const obj = {
      ProductName,
      ProductDescription,
      ProductPrice
    };
    this
      .http
      .post(`${this.uri}/update/${id}`, obj)
      .subscribe(res => console.log('Done'));
}

Okay, now write the updateProduct() function inside product-edit.component.ts file.

// product-edit.component.ts

updateProduct(ProductName, ProductDescription, ProductPrice, id) {
    this.route.params.subscribe(params => {
      this.ps.updateProduct(ProductName, ProductDescription, ProductPrice, params.id);
      this.router.navigate(['products']);
    });
  }

In the above function, we get the form values from the HTML forms and sends the PUT request to the node server with the updated values, and at the backend, the update function will call and update the values inside the MongoDB database.

Save the file, and you will be able to update the data.

#Step 17: Delete Form Data.

So, if you find no error on the console, then you can successfully update the form data.

I have already written edit and update service to make API calls. So till now, we have completed the Create, Read, Update task of this Angular 8 Tutorial with CRUD Example post. Now, take a look at Delete or remove the data from the database.

Now, we need to define the click event on the delete button inside the product-get.component.html file.

<!-- product-get.component.html -->

<tbody>
      <tr *ngFor="let product of products">
          <td>{{ product.ProductName }}</td>
          <td>{{ product.ProductDescription }}</td>
          <td>{{ product.ProductPrice }}</td>
          <td><a [routerLink]="['/edit', product._id]" class="btn btn-primary">Edit</a></td>
          <td><a (click) = "deleteProduct(product._id)" class="btn btn-danger">Delete</a>
      </tr>
  </tbody>

Now, write the deleteProduct function inside the product-get.component.ts file.

// product-get.component.ts

deleteProduct(id) {
    this.ps.deleteProduct(id).subscribe(res => {
      this.products.splice(id, 1);
    });
}

The above function will send the ID to the server to delete the row from the database, and on the frontend, we are using the Javascript Splice function to remove the data from the Angular application.

Now, create deleteProduct() function inside the product.service.ts file. We will send an ID in this AJAx request to delete the data from the backend.

// products.service.ts

deleteProduct(id) {
    return this
              .http
              .get(`${this.uri}/delete/${id}`);
  }

Finally, I completed the delete data or remove data functionality.

So, in this tutorial, we have completed the CRUD Functionality in Angular 8. I have also put the code on Github.

If you have any doubt in this Angular 8 Tutorial With CRUD Example, then ask in a comment below. If I were busy, then some will surely reply to you.

GITHUB CODE

Recommended Posts

Angular 8 File Upload Tutorial

Angular 8 HttpClient Example

Angular 8 Updates And Summary

How To Update Angular CLI To Version 8

Angular 8 Forms Tutorial

Angular NgFor Tutorial With Example

Angular NgStyle Example

Angular Modal Tutorial With Example

Angular NgClass Example

Angular Drag and Drop Tutorial

39 Comments
  1. N S Patil says

    Hi krunal,

    Thank you very much for this first.

    There is one thing which i found, when we update the record it is getting updating in database but not getting reflecting get products view however it reflects when we refresh the get products view.
    As I am seeing yoy have use this.router.navigate([‘products’]); in updateProduct method. is any additional configuration need to set to get immediate refection on viewproducts page as soon as we update the record.

    Thanks
    N s Patil

  2. Anon says

    You need to navigate when the update is done.

    Change the http.post in the updateProduct function in products.service.ts for this:

    this.http.post(`${this.uri}/update/${id}`, obj).subscribe(res => this.router.navigate([‘products’]));

    This is a quick way to fix this.

  3. Hans Hellberg says

    Thanks very impressive Tutorial.
    What happened to the ClientApp folder, Is that only used in Visual Studio ASP.NET projects?

  4. Hans Hellberg says

    I made it to the
    #Step 8: Configure the HttpClientModule,
    And my browser looks like this
    https://drive.google.com/file/d/19Az-Z3Iv4AaiBicmPkBrbvktNHB5U4b4/view?usp=sharing

    Any ide what went wrong?

  5. Levent says

    Very nice tutorial, but how can we ignore that warning when you are doing a tutorial for Angular 8. Do you have an editing solution without mixing the [(ngModel)] with Reactive Forms?

  6. Kaleem says

    i followed your angular 7 CRUD and there was the same warning. i thought this time you rectify this, but you did not. why?

  7. Matt says

    After completing this tutorial, I tried to run ng build –prod and got this:

    ERROR in srcappproduct-editproduct-edit.component.html (41,17) expected 4 arguments, but got 3

    1. multiv123 says

      I’m getting the same error.

  8. jesus says

    hi great tutorial, Im following, but, unfortunely, when I try to start the nodemon server to start to the next step for connect to the mongoDb , the terminal show me this error

    [nodemon] 1.19.1
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching: *.*
    [nodemon] starting `ng serve server`
    An unhandled exception occurred: Project ‘server’ does not support the ‘serve’ target.
    See “D:\Users\FANAZC~1.FAR\AppData\Local\Temp\ng-B48UQn\angular-errors.log” for further details.
    [nodemon] failed to start process, “ng serve” exec not found
    [nodemon] Error

    can you help me to solve this, in google I didn´t find info

    1. Vijay says

      Hi @Jesus Says

      You must run the nodemon server inside the API path in cmd prompt.
      like this

      C:\Users\MEAN\store\api>nodemon server
      [nodemon] 1.19.1
      [nodemon] to restart at any time, enter `rs`
      [nodemon] watching: *.*
      [nodemon] starting `node server.js`
      Listening on port 4000
      Database is connected

      1. jesus says

        LOL yeah , I realized of that mistake thanks a lot, today finally ended the crud (thanks to my job I didn´t) best regards

  9. mahi says

    zone-evergreen.js:2952 POST http://localhost:4000/products/add 400 (Bad Request)
    scheduleTask @ zone-evergreen.js:2952
    scheduleTask @ zone-evergreen.js:378
    onScheduleTask @ zone-evergreen.js:272
    scheduleTask @ zone-evergreen.js:372
    scheduleTask @ zone-evergreen.js:211
    scheduleMacroTask @ zone-evergreen.js:234
    scheduleMacroTaskWithCurrentZone @ zone-evergreen.js:1107
    (anonymous) @ zone-evergreen.js:2985
    proto. @ zone-evergreen.js:1428
    (anonymous) @ http.js:2065
    _trySubscribe @ Observable.js:42
    subscribe @ Observable.js:28
    (anonymous) @ subscribeTo.js:20
    subscribeToResult @ subscribeToResult.js:7
    _innerSub @ mergeMap.js:59
    _tryNext @ mergeMap.js:53
    _next @ mergeMap.js:36
    next @ Subscriber.js:49
    (anonymous) @ scalar.js:4
    _trySubscribe @ Observable.js:42
    subscribe @ Observable.js:28
    call @ mergeMap.js:21
    subscribe @ Observable.js:23
    call @ filter.js:13
    subscribe @ Observable.js:23
    call @ map.js:16
    subscribe @ Observable.js:23
    addProduct @ products.service.ts:21
    addProduct @ product-add.component.ts:26
    (anonymous) @ ProductAddComponent.html:39
    handleEvent @ core.js:34789
    callWithDebugContext @ core.js:36407
    debugHandleEvent @ core.js:36043
    dispatchEvent @ core.js:22533
    (anonymous) @ core.js:33721
    (anonymous) @ platform-browser.js:1789
    invokeTask @ zone-evergreen.js:391
    onInvokeTask @ core.js:30885
    invokeTask @ zone-evergreen.js:390
    runTask @ zone-evergreen.js:168
    invokeTask @ zone-evergreen.js:465
    invokeTask @ zone-evergreen.js:1603
    globalZoneAwareCallback @ zone-evergreen.js:1629
    Show 13 more frames
    core.js:7187 ERROR HttpErrorResponse {headers: HttpHeaders, status: 400, statusText: “Bad Request”, url: “http://localhost:4000/products/add”, ok: false, …}
    defaultErrorLogger @ core.js:7187
    handleError @ core.js:7239
    next @ core.js:31614
    schedulerFn @ core.js:27846
    __tryOrUnsub @ Subscriber.js:185
    next @ Subscriber.js:124
    _next @ Subscriber.js:72
    next @ Subscriber.js:49
    next @ Subject.js:39
    emit @ core.js:27808
    (anonymous) @ core.js:30943
    invoke @ zone-evergreen.js:359
    run @ zone-evergreen.js:124
    runOutsideAngular @ core.js:30830
    onHandleError @ core.js:30940
    handleError @ zone-evergreen.js:363
    runTask @ zone-evergreen.js:171
    invokeTask @ zone-evergreen.js:465
    ZoneTask.invoke @ zone-evergreen.js:454
    timer @ zone-evergreen.js:2650
    setTimeout (async)
    scheduleTask @ zone-evergreen.js:2671
    scheduleTask @ zone-evergreen.js:378
    onScheduleTask @ zone-evergreen.js:272
    scheduleTask @ zone-evergreen.js:372
    scheduleTask @ zone-evergreen.js:211
    scheduleMacroTask @ zone-evergreen.js:234
    scheduleMacroTaskWithCurrentZone @ zone-evergreen.js:1107
    (anonymous) @ zone-evergreen.js:2686
    proto. @ zone-evergreen.js:1428
    hostReportError @ hostReportError.js:2
    error @ Subscriber.js:158
    _error @ Subscriber.js:75
    error @ Subscriber.js:55
    _error @ Subscriber.js:75
    error @ Subscriber.js:55
    _error @ Subscriber.js:75
    error @ Subscriber.js:55
    notifyError @ OuterSubscriber.js:7
    _error @ InnerSubscriber.js:14
    error @ Subscriber.js:55
    onLoad @ http.js:1961
    invokeTask @ zone-evergreen.js:391
    onInvokeTask @ core.js:30885
    invokeTask @ zone-evergreen.js:390
    runTask @ zone-evergreen.js:168
    invokeTask @ zone-evergreen.js:465
    invokeTask @ zone-evergreen.js:1603
    globalZoneAwareCallback @ zone-evergreen.js:1640
    load (async)
    customScheduleGlobal @ zone-evergreen.js:1742
    scheduleTask @ zone-evergreen.js:378
    onScheduleTask @ zone-evergreen.js:272
    scheduleTask @ zone-evergreen.js:372
    scheduleTask @ zone-evergreen.js:211
    scheduleEventTask @ zone-evergreen.js:237
    (anonymous) @ zone-evergreen.js:1911
    (anonymous) @ http.js:2053
    _trySubscribe @ Observable.js:42
    subscribe @ Observable.js:28
    (anonymous) @ subscribeTo.js:20
    subscribeToResult @ subscribeToResult.js:7
    _innerSub @ mergeMap.js:59
    _tryNext @ mergeMap.js:53
    _next @ mergeMap.js:36
    next @ Subscriber.js:49
    (anonymous) @ scalar.js:4
    _trySubscribe @ Observable.js:42
    subscribe @ Observable.js:28
    call @ mergeMap.js:21
    subscribe @ Observable.js:23
    call @ filter.js:13
    subscribe @ Observable.js:23
    call @ map.js:16
    subscribe @ Observable.js:23
    addProduct @ products.service.ts:21
    addProduct @ product-add.component.ts:26
    (anonymous) @ ProductAddComponent.html:39
    handleEvent @ core.js:34789
    callWithDebugContext @ core.js:36407
    debugHandleEvent @ core.js:36043
    dispatchEvent @ core.js:22533
    (anonymous) @ core.js:33721
    (anonymous) @ platform-browser.js:1789
    invokeTask @ zone-evergreen.js:391
    onInvokeTask @ core.js:30885
    invokeTask @ zone-evergreen.js:390
    runTask @ zone-evergreen.js:168
    invokeTask @ zone-evergreen.js:465
    invokeTask @ zone-evergreen.js:1603
    globalZoneAwareCallback @ zone-evergreen.js:1629
    Show 41 more frames
    products.service.ts:19 {ProductName: “jbkj”, ProductDescription: “hfh”, ProductPrice: “yu”}
    :4000/products/add:1 POST http://localhost:4000/products/add 400 (Bad Request)
    core.js:7187 ERROR HttpErrorResponse {headers: HttpHeaders, status: 400, statusText: “Bad Request”, url: “http://localhost:4000/products/add”, ok: false, …}

    1. Joey says

      // add port variable in app.listen like this so node server is available
      const server = app.listen(port,function(){
      console.log(‘Listening on port ‘ + port);
      });

      1. Diego says

        I had the same problem. In which file do I add this code?

  10. Vijay says

    Hi @Jesus Says

    You must run the nodemon server inside the API path in cmd prompt.
    like this

    C:\Users\MEAN\store\api>nodemon server
    [nodemon] 1.19.1
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching: *.*
    [nodemon] starting `node server.js`
    Listening on port 4000
    Database is connected

  11. Janani says

    In step 12, npm install nodemon –save-dev did not work for me. But npm install -g nodemon worked. (reference: https://www.npmjs.com/package/nodemon)

  12. g says

    ERROR Error: “Uncaught (in promise): Error: Cannot match any routes. URL Segment: ‘products/edit/5d4948982897c040147caed3’
    noMatchError@http://localhost:4200/vendor.js:93863:16
    apply/<@http://localhost:4200/vendor.js:93827:28
    error@http://localhost:4200/vendor.js:104256:31
    _error@http://localhost:4200/vendor.js:101306:26
    error@http://localhost:4200/vendor.js:101286:18
    _error@http://localhost:4200/vendor.js:101306:26
    error@http://localhost:4200/vendor.js:101286:18
    _error@http://localhost:4200/vendor.js:101306:26
    error@http://localhost:4200/vendor.js:101286:18
    ………………………….
    Any idea ?

    1. g says

      My “Edit” button doesn’t work.

      1. g says

        Solved. In app-routing.module.ts
        you need path: ‘products/edit/:id’,
        not path: ‘edit/:id’,
        Thanks.

      2. g says

        Solved.
        You need path: ‘products/edit/:id’,
        not path: ‘edit/:id’,
        Thanks.

      3. g says

        path: ‘products/edit/:id’,

  13. Lorenzo says

    Hi krunal,

    Thank you very much for this great tutorial.
    I have a question:
    I tried to insert a inside the form but when I try to take the value of the select the console.log prints “undefined”

    This is my html

    Order

    Select
    One
    Two
    Three

    I think the error is here.
    (In the future I will replace the options with an ngfor)

    Where am I wrong?
    Can you give an example also for the ?

    Thanks a lot.

  14. g says

    path: ‘products/edit/:id’,

  15. Alex Vieira says

    Awesome

    The Tutorial is really complete and extraordinarily explained and it is easy to understand, even for new users of angular.

    Also bring us to mongodb that is a incredible NOSQL Database and sure the Node.js (Awesome).

  16. Francisco Oliveira says

    I got a message saying ERR_EMPTY_RESPONSE, any idea?

  17. Richard Lockyer says

    In step 12 nodemon should be installed globally otherwise when you start it you will get an error stating:
    -bash: nodemon: command not found

    Try

    sudo npm install -g nodemon –save-dev

  18. Francisco Oliveira says

    It worked already thx

    If i want to use more than 1 schema how can i do it? Let’s say i want a login page and create accounts,do i need another script like server.js just for login routes?

  19. Multiv123 says

    Hey, I’m trying to get search to work, but the query string keeps returning all the values from the database.
    How do I filter the values from the querystring?

  20. g says

    DELETE is not working properly. In product-get.component.html you give to the
    deleteProduct function product._id which is not an id it’s just a hash.
    so why would the splice in product-get.component.ts in deleteProduct function work?
    I removed the splice and it works.

  21. atul says

    super cool very useful to me thank you very much

  22. Diego says

    [Error] Failed to load resource: the server responded with a status of 404 (Not Found) (add, line 0)

    And it happens for add, edit, like the folders and files wasn’t there. I think it could be writing on db forbidden on my Mac or uri problems on code, may be….

  23. nalini says

    I am getting below error in the debugger console when I try to add

    Object { headers: {…}, status: 400, statusText: “Bad Request”, url: “http://localhost:4000/products/add”, ok: false, name: “HttpErrorResponse”, message: “Http failure response for http://localhost:4000/products/add: 400 Bad Request”, error: “unable to save to database” }

  24. nalini says

    please ignore above is solved after passing correct data

    1. Diego says

      How do you solved this?

      1. Youenn says

        try to pass correct data like :
        name : Apple
        desc : apple from a tree,
        price : 100

  25. Youenn says

    if someone is having trouble with nodemon server try this : npx nodemon server.js

  26. Palanisamy says

    Great tutorial !! Simply awesome !! Step by step guide for anybody starting to learn server side java script programming. I will definitely encourage anybody to go through this tutorial.

    Cheers !
    Pal

  27. Lucas Alves says

    Is there any way to do with MySQL instead of mongo?

    1. Zak Leber says

      yes but is not good idea

Leave A Reply

Your email address will not be published.

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