AppDividend
Latest Code Tutorials

Angular 9 Tutorial: How to Build Angular 9 CRUD Example

9

Angular 9 provides a base to build rich, client-side web applications with specialization in data-binding. Angular has a component architecture inspired by web components. Everything in Angular is a small component. This way, angular becomes more and more modular in which it is easy to handle in large enterprise-level applications. With Angular, you can even build native apps, single-page applications using its routing module.

Angular 9 Tutorial

Angular source code is written in TypeScript. If you are a newbie to TypeScript, then check out the TypeScript Example guide in this blog. TypeScript is a superset of Javascript.

Advantages of TypeScript

  1. TypeScript uses ES2015 or ES6 classes.
  2. TypeScript use Modules. So we can import or export any module.
  3. TypeScript has strong typing; that means if you define a type for a particular variable, then you can not assign the different types to that variable. It is a strongly typed language and not loosely coupled language.
  4. TypeScript has function signatures.
  5. We can write Javascript code in TypeScript file. So it is not necessary to used strong typed code. But in large web applications, strong typing is beneficial and saves lots of time.

Angular 9 Architecture

The angular framework is built upon components. The angular app is bootstrapping of the first parent component, much like the HTML DOM tree that starts with the HTML element and then branches down from there. 

Angular runs on the component tree model. After Angular loads the first component with a bootstrap call, it then looks within the component’s HTML view and sees if it has any nested components. If it does, then Angular finds matches and runs the appropriate component code on those. This process repeats for each component down the tree. 

The component in Angular is used to render the portion of HTML and provide the functionality to that portion. It does this through the Component class in which you can define application logic for the component.

Angular 9 Directives

Angular directives provide the functionality that can transfer the DOM.

There are two types of directives in Angular.

  1. Structural
  2. Attribute

 Structural directives modify layout by altering HTML elements in a DOM. 

Attribute directives change the behavior or appearance of the existing DOM element.

You can apply the directives to the existing DOM elements.

Like a component, the directive gets configured with the selector that Angular will use to find the match and apply the directive. You apply the directive in different ways.

You can write an attribute on the element that matches your selector, or you can use a template syntax to add a directive and an assignment statement. 

In addition to creating your directives, Angular comes with several directives out of the box to handle standard web app constructs by conditionally rendered components based on some expression, looping out items to render.

Angular 9 Data Binding

The Angular is much more than just the engine for rendering markup templates through components. One of the Angular’s strengths is the ease of binding data to views and working with data in those views. 

The most popular way of displaying data in the view template is via interpolation, where you use the set of curly braces around a component property to tell Angular to render the content of that property. 

You can also use directives, both inbuilt Angular ones, and ones you create, to help display data

Directives give you the client-side power app to add logic to your views, similar to how you would on the server-side.

The template syntax in Angular is pretty sound and allows you to achieve a lot of things when it comes to working with data in the views.

We can wire up the click events to DOM elements that change data that you’ve displayed elsewhere, and Angular will control the update of that data visually. 

There are many components to the template syntax. In addition to interpolation and inbuilt directives, the syntax has constructs and patterns, such as template expressions and statements, a binding syntax for the property, attribute, class, and style bindings, event binding, and template expression operators.

Angular 9 Dependency Injection

Dependency injection or DI is the concept of inversion of control, or IoC for short, where you architect your code in such a way that you provide modules with other modules it needs to get some work done, instead of having the modules go out and get other modules on their own.

DI(Dependency Injection) allows you to write decoupled code that is simpler to unit test and to work with. In the Angular environment, this will enable you to write these modular components, and even services within your applications, and tell Angular what you want to use and where you want to use them. 

Angular will control constructing instances of those and sending them to your code where needed. The most common place you use Dependency Injection is in the class constructors. So constructors for directives, components, pipesand services you write can leverage the way Angular does Dependency Injection.

You can declare types on your constructor parameters with some help from TypeScript, and Angular will interpret that and make sure you receive the instance of that type when your constructors run.

You can also leverage DI through things like component metadata, properties, directives, and providers.

You can even do some Dependency Injection at the bootstrap phase of an Angular app, setting up your dependency graph when your app starts up and getting that delivered through all aspects of your app. One of the powers of DI is the ability to replace a dependency at any phase of application code.

Angular 9 Services

Services in Angular are more of a predetermined pattern. There is nothing unusual in the Angular framework that defines some code as a service. 

Service means we refer to a JavaScript class or function that we have written to encapsulate some logic as the service for our application.

When you write your components, it is preferred to craft the classes for these in a way that the class logic only consists of data to and from the view and adding functionality to a view. 

So if you don’t put the business logic in your components and directives, then where do you put it? The answer is services.

You can write the JavaScript class that deals with finding the record from API or external data source and returning it as an objectThis would be a service. Then using Angular’s DI, you can specify that your Angular component is going to use your service, and from within your component logic, you can request the database record from your service and make it available to your view template. 

These services that you write can also leverage DI, so you can create constructors and specify parameter types, with the help of some TypeScript, and Angular will provide your service instance with the appropriate dependencies.

There’s nothing in the services that you write that do them any Angular-type service. They are just plain old JavaScript logic code. And with the power of DI, they are given other code to work with, without needing to know that that code is Angular-specific code. 

So when you leverage writing services in your app code, you get the benefit of writing JavaScript logic that is in no way coupled to the Angular framework. This is one of the excellent advantages of Angular.

Angular 9 Routing

Angular provides a router module out of the box for doing just that. It supports configuring route paths to components, route params, so you can have variables in the URL, a directive for specifying wherein the template the routed component will display, creating child routes, all of the navigation interception. 

It also handles history state, modifying the way a browser handles it by default, changing the way a browser handles it by default, so that back and forward actions by the user so that back and forward actions by the user will result in Angular route changes which will result in Angular route changes.

Using the router gives your apps a familiar feel to the standard client-server website experience users have grown accustomed to. Users have become used to it. But it also provides you a way to load different components through a set of configuration instructions and links, of configuration instructions and links, rather than a bunch of conditional logic scripting.

Angular 9 HttpClient

Angular framework communicates with backend servers through REST APIs using either the XMLHttpRequest interface or the fetch() API.

Angular HttpClient makes use of an XMLHttpRequest interface that supports both modern and legacy browsers.

Angular HttpClient is available from the @angular/common/http package and has a simplified API interface and robust features such as typed request, easy testability, response objects, request, response interceptors, reactive APIs with RxJS Observables, and streamlined error handling.

So, these are the primary component architecture of Angular. Let’s talk about the latest features of Angular 9.

New Features of Angular 9

Default Ivy

Now, with the release of Angular 9, the Ivy renderer is a default Angular compiler, so you do not need to write anything new in your tsconfig file to enjoy Ivy.

If you used Angular 8 in your old projects, you would remember that the Ivy renderer was already available to you, but you need to opt-in to use that. For example, you had to go to your tsconfig.json file and add the line below to it.

"angularCompilerOptions": {    "enableIvy": true  }

ModuleWithProviders Support

This new feature is mostly for library creators. If you have used ModuleWithProviders before Angular 9, then you may or may not have been actively using it. Still, now in this version, you always have to use the generic ModuleWithProviders<T> type to show your module type.

The following code is an old declaration.

@NgModule({ ...}) export class MyModule { static forRoot(config: SomeConfig): ModuleWithProviders {  
   return {  
         ngModule: SomeModule,  
         providers: [{ provide: SomeConfig, useValue: config }]  
   };  
 }  
}

Now, they should look like the below code.

@NgModule({ ...})  
export class MyModule {  
  static forRoot(config: SomeConfig): ModuleWithProviders<**SomeModule**>  
{  
   return {  
         ngModule: SomeModule,  
         providers: [{ provide: SomeConfig, useValue: config }]  
   };  
 }  
}

If you do not know how to migrate your old angular project to Angular 9, then check out the Angular 9 project update guide.

Dependency Injection Changes in Core

For dependency injection, a new version of Angular also comes with a little improvement. This is not such a significant change, but some support has been added for the providedIn value section of dependency injections.

See the following code.

@Injectable({    providedIn: 'platform'  })  class MyService {...}

Service Worker Updates

In this latest version of Angular, for service workers, the deprecated versioned files option in a service worker asset group config has been removed. Now, it looks like this.

"assetGroups": [  
  {  
    "name": "test",  
    "resources": {  
      "files": [  
        "/**/*.txt"  
      ]  
    }  
  }  
]

Angular i18n Improvements

Angular is a JavaScript MVC framework that has long supported internationalization. With the Angular CLI, you can generate standard codes that would help create translator files so that your angular app can be published in multiple languages. This process has been further refactored by an Angular team on Ivy to make it simpler to add compile-time inlining.

Covering in Angular 9 CRUD Tutorial with Example

  1. How to install Angular CLI 9,
  2. How to create the Angular 9 project using Angular CLI,
  3. How to make the Angular components, routing, and navigation between them.
  4. How to create and inject Angular services.
  5. How to send HTTP GET, POST requests to servers using HttpClient.
  6. How to setup node.js web server.
  7. How to install and configure express web framework.
  8. How to create express routes and models.

Angular 9 Tutorial Workflow

Let’s create two separate projects for this Angular 9 tutorial. One is for Angular Frontend, and another is for Node.js backend. That means one is for the frontend, and one is for the backend.

We will create a backend API using node and mongodb, which deals with saving, modifying, and removing the form values from Database, and the frontend will consume that API; for example, it shows the data from the backend and post the data to node server and store the values in the Database.

For this tutorial, I am using the following tech stacks. I have described it with the current latest version. Yours might be different depending on the future.

  1. Node v13.3.0
  2. NPM v6.13.7
  3. AngularCLI v9.0.3
  4. MongoDB shell version v3.6.3
  5. MongoDB version v3.6.3
  6. Mac OS Catalina

In this blog, I have already written an Angular 8 Tutorial, Angular 7 CRUD, and Angular 6 CRUD tutorials briefly. Now, the Angular community has released the next version, which is Angular 9.

So, In this MEAN Stack tutorial, we will see Angular RoutingAngular Forms, and on the back end side, we use the Node.js and Express.js to control the data and to store the data at server side. We will use MongoDB as a DBMS.

Angular 9 CRUD Example

In this Angular 9 crud example, we will create an Angular app of Namaste Trump, which implementing the CRUD operation.

On this day, 24th Feb 2020, Donald Trump is arriving in India. So, We will create a simple crud app that creates, edits, updates and deletes trump’s family member.

This app will use REST API that store and fetch the required data from the node, express web API, store the network data using POST request, put case changes, and delete data.

So, we will learn how to use the HTTPClient module to access REST API, using Angular Form, using Routing and Navigation, and create a simple Calendar. Also will see how to send GET requests and process HTTP responses from REST API servers in your Angular 9 application using HttpClient for fetching and consuming JSON data.

Let’s start the Angular 9 tutorial to build a crud application.

Step 1: Setting up Angular CLI v9

If you have not installed Angular CLI in the past, then follow the below command.

// For Mac and Linux

sudo npm install -g @angular/cli

// For Windows

Open CMD in Administrator mode and hit the following command.

npm install -g @angular/cli

Now, run the following command to check the Angular CLI version.

ng version

 

Setting up Angular CLI v9

In the next step, we initialize the new angular9crud project.

Step 2: Initializing a New Angular 9 CRUD Project

Type the following command to create the new Angular 9 project.

ng new angular9crud

The CLI will ask you two questions like If Would you like to add Angular routing? Type y for Yes, and the second question is which stylesheet format would you like to use? Choose CSS. You can choose anyone based on your requirements.

This will instruct the CLI to set up routing in our project automatically, so we’ll only need to add the routes for our angular components to implement navigation in our application.

You can check a file called app-routing.module.ts file inside the src >> app directory.

Next, navigate to your project’s directory and run the local development server using the following command.

ng serve --open
➜  angular9crud git:(master) ng serve --open
0% compiling
Compiling @angular/core : es2015 as esm2015

Compiling @angular/common : es2015 as esm2015

Compiling @angular/platform-browser : es2015 as esm2015

Compiling @angular/platform-browser-dynamic : es2015 as esm2015

Compiling @angular/router : es2015 as esm2015

chunk {main} main.js, main.js.map (main) 60.6 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 140 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 6.15 kB [entry] [rendered]
chunk {styles} styles.js, styles.js.map (styles) 9.72 kB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 2.97 MB [initial] [rendered]
Date: 2020-02-24T04:43:10.082Z - Hash: 7aef88a18f3aab2ca218 - Time: 22469ms
** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
: Compiled successfully.

Date: 2020-02-24T04:43:11.341Z - Hash: 7aef88a18f3aab2ca218
5 unchanged chunks

Time: 857ms
: Compiled successfully.

Using the “–open” parameter will automatically open this Angular 9 app in the default browser.

The default URL is: http://localhost:4200

 

Angular 9 Tutorial

You should leave the development server running and start a new command-line interface for running the CLI commands of the next steps.

Step 3: Install Bootstrap 4 CSS Framework

Next, install a Bootstrap 4 CSS library using the following command. It is not the necessary step, and you can choose your CSS Framework as well. For example, most people will use Angular Material components.

➜  angular9crud git:(master) ✗ npm install bootstrap --save

> fsevents@1.2.11 install /Users/krunal/Desktop/code/angular/angular9crud/node_modules/watchpack/node_modules/fsevents
> node-gyp rebuild

  SOLINK_MODULE(target) Release/.node
  CXX(target) Release/obj.target/fse/fsevents.o
  SOLINK_MODULE(target) Release/fse.node

> fsevents@1.2.11 install /Users/krunal/Desktop/code/angular/angular9crud/node_modules/webpack-dev-server/node_modules/fsevents
> node-gyp rebuild

  SOLINK_MODULE(target) Release/.node
  CXX(target) Release/obj.target/fse/fsevents.o
  SOLINK_MODULE(target) Release/fse.node
npm WARN bootstrap@4.4.1 requires a peer of jquery@1.9.1 - 3 but none is installed. You must install peer dependencies yourself.
npm WARN bootstrap@4.4.1 requires a peer of popper.js@^1.16.0 but none is installed. You must install peer dependencies yourself.

+ bootstrap@4.4.1
added 139 packages from 33 contributors, updated 1 package and audited 15251 packages in 24.033s

33 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Now, add the bootstrap css file inside an angular.json file.

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

Step 4: Create Angular components

We will create three Angular components.

ng g c member-add --skipTests=true
ng g c member-get --skipTests=true
ng g c member-edit --skipTests=true

See the output.

➜  angular9crud git:(master) ✗ ng g c member-add --skipTests=true
CREATE src/app/member-add/member-add.component.css (0 bytes)
CREATE src/app/member-add/member-add.component.html (25 bytes)
CREATE src/app/member-add/member-add.component.ts (290 bytes)
UPDATE src/app/app.module.ts (489 bytes)
➜  angular9crud git:(master) ✗ ng g c member-get --skipTests=true
CREATE src/app/member-get/member-get.component.css (0 bytes)
CREATE src/app/member-get/member-get.component.html (25 bytes)
CREATE src/app/member-get/member-get.component.ts (290 bytes)
UPDATE src/app/app.module.ts (585 bytes)
➜  angular9crud git:(master) ✗ ng g c member-edit --skipTests=true
CREATE src/app/member-edit/member-edit.component.css (0 bytes)
CREATE src/app/member-edit/member-edit.component.html (26 bytes)
CREATE src/app/member-edit/member-edit.component.ts (294 bytes)
UPDATE src/app/app.module.ts (685 bytes)
➜  angular9crud git:(master) ✗

For this demo, we won’t need the tests file. So, we won’t create it.

All three components are automatically registered inside the app.module.ts file.  Those components will automatically be registered to the app.module.ts. Next, open and edit `src/app/app-routing.module.ts` then add these imports.

Step 5: Create Angular 9 Routing

Import all the newly created angular components inside the app-routing.module.ts file.

// app-routing.module.ts

import { MemberAddComponent } from './member-add/member-add.component';
import { MemberEditComponent } from './member-edit/member-edit.component';
import { MemberGetComponent } from './member-get/member-get.component';

Add these arrays to the existing routes constant that contain route for above-added components.

// app-routing.module.ts

const routes: Routes = [
  {
    path: 'member/create',
    component: MemberAddComponent
  },
  {
    path: 'edit/:id',
    component: MemberEditComponent
  },
  {
    path: 'members',
    component: MemberGetComponent
  }
];

So, our final app-routing.module.ts file looks like below.

// app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { MemberAddComponent } from './member-add/member-add.component';
import { MemberEditComponent } from './member-edit/member-edit.component';
import { MemberGetComponent } from './member-get/member-get.component';

const routes: Routes = [
  {
    path: 'member/create',
    component: MemberAddComponent
  },
  {
    path: 'edit/:id',
    component: MemberEditComponent
  },
  {
    path: 'members',
    component: MemberGetComponent
  }
];

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

Open and edit “src/app/app.component.html,” and you will see the existing router outlet.

Next, modify this HTML page to fit the CRUD page.

Write the below code inside an 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="member/create" class="nav-link" routerLinkActive="active">
          Create Member
        </a>
      </li>
      <li class="nav-item">
        <a routerLink="members" class="nav-link" routerLinkActive="active">
          Members
        </a>
      </li> 
    </ul>
  </div>
</nav>

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

Go to the browser and click on both links. You can see that we can see the different components based on navigation.

Step 6: Configure the Angular Routing Progress Indicator.

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

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

If you install the ng2-slim-loading-bar library, then it is not compatible with an Angular 9.

If we want to bridge a gap between Angular 9 and the third-party packages, we need to install the following library. That is it.

npm install rxjs-compat --save

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

// app.module.ts

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

imports: [
    ...
    SlimLoadingBarModule
],

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

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

Step 7: Add Angular router events.

Angular RouterModule gives us the following events.

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

Now, add the below 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 = 'angular9tutorial';
  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 app that while we navigate from one component to another component, we want to display the router progress indicator. 

When the user clicks the other link, the angular progress indicator start showing, and when the navigation is complete, it will stop displaying the indicator. It is like a loader that tells the user that some kind of process is ongoing.

So, we can serve the best User experience for the user while navigating different routes.

Inside the code, what is happening is that it intercepts the routing event and adds the loading bar component to a loading route so that we can see the routing indication every time we change the routes.

The final step is to display a routing indicator.

So, we need to insert the ng2-slim-loading-bar directive inside an app.component.html file at the top of the page.

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

<ng2-slim-loading-bar color="red"></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="member/create" class="nav-link" routerLinkActive="active">
          Create Member
        </a>
      </li>
      <li class="nav-item">
        <a routerLink="members" class="nav-link" routerLinkActive="active">
          Members
        </a>
      </li> 
    </ul>
  </div>
</nav>

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

Now, go to the terminal to see if there are any errors, and if there are no errors, then go to the application and change the routes, and you can see that now we can see the routing indicator while we are changing the different angular routes.

Step 8: Add Bootstrap 4 HTML form

Add the following HTML code inside the add-member.component.html file.

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

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

Save the file and go to the: http://localhost:4200/member/create

 

How To Build Angular 9 CRUD App

Step 9: Create Angular 9 form validation

Web apps are usually doing one of two things, either displaying data to the user or collecting the data from the user. The way we collect the data through web apps is the use of web forms and more specific form elements. 

When we build the forms in web apps, we architect them to do a standard set of things in a way to collect data, in a way to know when the user changes data, for validating data, and for displaying errors to users. 

Since the forms are a standard component of web apps, Angular has a dedicated section to help when working with forms.

Angular forms provide inbuilt data validators and the ability to create your own. Async validators that can run validation as a user changes input value. It also wraps up form fields into an object structure for ease of use on submission. 

There are two conventional approaches to building forms in Angular. 

  1. Template-driven: where the majority of the form logic is crafted in the template markup.
  2. Model-driven: where the majority of the form logic, is crafted in the component class.

Overall, the form support built into Angular covers a wide range of input collection scenarios.

We will use a ReactiveFormsModule approach. Now, if you are a newbie to Angular Form Validation, then check out my article Angular Form Validation Example Tutorial on this blog.

The next step is to import a ReactiveFormsModule inside the app.module.ts file. It comes with the Angular project out of the box.

// app.module.ts

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

imports: [
    ...
    ReactiveFormsModule
],

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

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

Also, create the constructor and instantiate a FormBuilder.

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

// member-add.component.ts

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

@Component({
  selector: 'app-member-add',
  templateUrl: './member-add.component.html',
  styleUrls: ['./member-add.component.css']
})
export class MemberAddComponent implements OnInit {
  angForm: FormGroup;
  constructor(private fb: FormBuilder) {
    this.createForm();
  }

  createForm() {
    this.angForm = this.fb.group({
      MemberName: ['', Validators.required],
      MemberBio: ['', Validators.required],
      MemberAge: ['', Validators.required]
    });
  }

  ngOnInit(): void {
  }

}

We have used the form builder to handle all the validation. So in the constructor, we are creating a form with all the validation rules for specific fields. In our example, there are three form fields.

If an input text is empty, then it will give us an error, and we need to show that error to the user.

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

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

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

Go to the browser, try not to fill any value inside any input box, then it will display the errors.

 

Create Angular 9 form validation

Step 10: Add and configure the HttpClientModule

Most frontend applications communicate with the backend services over the HTTP protocol. Modern browsers support the main two different APIs for making HTTP requests: the XMLHttpRequest and fetch API.

The HttpClient in @angular/common/http module offers the simplified client HTTP API for Angular apps that rests on the XMLHttpRequest interface exposed by the browsers.

Additional benefits of an HttpClient include the request, testability features, typed request, response objects, response interception, Observables APIs, and smooth error handling.

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

// app.module.ts

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

imports: [
   ...
    HttpClientModule
 ],

Step 11: Create the TypeScript model file.

Inside the src >> app directory, create a file called Member.ts and add the following code.

// Member.ts

export default class Member {
  MemberName: string;
  MemberBio: string;
  MemberAge: number;
}

Step 12: Create an Angular 9 Service file

All-access (POST, GET, PUT, DELETE) to the REST API will put in the Angular 9 Service. The response from the REST API emitted by Observable that can subscribe and read from the Components.

Hit the following command to generate a service file.

The main use of the service file is that we are adding all of our AJAX(network request) code inside that file.

So, it deals with the backend server and sends and retrieves the data from the backend server.

ng g service member --skipTests=true

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

// member.service.ts

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

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

  constructor() { }
}

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

// app.module.ts

import { MemberService } from './member.service';

providers: [ MemberService ],

Step 13: Submit form values to node server

Now, we will send an HTTP POST request with data to the Node.js server and store the data into a MongoDB database.

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

// member.service.ts

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

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

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

  constructor(private http: HttpClient) { }

  addMember(MemberName, MemberBio, MemberAge) {
    const obj = {
      MemberName,
      MemberBio,
      MemberAge
    };
    console.log(obj);
    this.http.post(`${this.uri}/add`, obj)
      .subscribe(res => console.log('Done'));
  }
}

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

Now, add the addMember function inside the member-add.component.ts file.

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

// member-add.component.ts

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

@Component({
  selector: 'app-member-add',
  templateUrl: './member-add.component.html',
  styleUrls: ['./member-add.component.css']
})
export class MemberAddComponent implements OnInit {
  angForm: FormGroup;
  constructor(private fb: FormBuilder, private ms: MemberService, private router: Router, ) {
    this.createForm();
  }

  createForm() {
    this.angForm = this.fb.group({
      MemberName: ['', Validators.required],
      MemberBio: ['', Validators.required],
      MemberAge: ['', Validators.required]
    });
  }

  addMember(MemberName, MemberBio, MemberAge) {
    this.ms.addMember(MemberName, MemberBio, MemberAge);
    this.router.navigate(['members']);
  }

  ngOnInit(): void {
  }

}

In the above code, what I have done is that I have imported the member.service.ts file. Then we have defined the addMember method.

That method is called when a user clicks on the create member button, and the flow will transfer over here. We get the MemberName, MemberBio, and MemberAge data inside the addMember function.

Inside that function, we are calling the member.service.ts file’s addMember() function and pass the three parameters with it.

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

Up until now, we have reached half of the Angular 9 CRUD example.

In the next half of the angular 8 tutorial, we will talk about the Node, Express, MongoDB, API, and stuff because this tutorial is Angular 9  CRUD Full Stack Example.

Now, we require to add the click event to the Add Member Button. So add the following code inside a member-add.component.html file.

<div class="form-group">
        <button type="submit" class="btn btn-primary"
        [disabled]="angForm.pristine || angForm.invalid"
        (click) = "addMember(MemberName.value, MemberBio.value, MemberAge.value)">Create Member</button>
</div>

So when there are no errors on the frontend, we can submit a form, and it will call the component’s addMember() function. From there, we will call an angular service, and that service will send an HTTP Post request to the Node.js server.

Step 14: Create a backend API in Node

Inside the angular root folder, create a folder called the API and go inside that folder. Remember, it will be an utterly separate project from the Angular frontend project.

So its node_modules folder is different from the Angular project.

Open the terminal inside the API directory and hit the following command. It will generate a package.json file using NPM. I do not want to specify each option one by one; so we are typing the following command with -y flag.

npm init -y

Install the following node-specific modules.

npm install express body-parser cors mongoose --save

If you do not want to restart the node server each time, you can install the nodemon server. What it does is that, when I change the server.js file, it restarts the node.js server automatically.

npm install nodemon --save-dev

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

We have installed a 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 a CORS module to receive the proper request at the backend server.

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

Now, inside an API folder, create a 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(port, function(){
        console.log('Listening on port ' + port);
    });

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

If you haven’t installed the MongoDB database previously in the machine, then install it and then start a MongoDB server using the following command.

mongod

So, Now, I have connected to the Database.

Create a file called DB.js inside the API project folder.

Write the following code inside the DB.js file.

// DB.js

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

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

Write a following code inside the server.js file to connect our MongoDB database 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, { useUnifiedTopology: true, 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 start the nodemon server.

nodemon server

So, right now, you have three servers running on our computers.

  1. Angular Development Server.
  2. Nodemon server.
  3. MongoDB server.

Remember, all three servers are running fine without any error; otherwise, our application won’t work.

Step 15: Create a route and model files.

Next step, we need to create the two folders inside the API folder called routes and models.

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

// Member.js

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

// Define collection and schema for Member
let Member = new Schema({
  MemberName: {
    type: String
  },
  MemberBio: {
    type: String
  },
  MemberAge: {
    type: Number
  }
},{
    collection: 'Member'
});

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

So, we have defined our schema for the Member collection. We have three fields called MemberName, MemberBio, MemberAge.

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

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

// member.route.js

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

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

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

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

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

//  Defined update route
memberRoutes.route('/update/:id').post(function (req, res) {
  Member.findById(req.params.id, function (err, member) {
    if (!member)
      res.status(404).send("Record not found");
    else {
      member.MemberName = req.body.MemberName;
      member.MemberBio = req.body.MemberBio;
      member.MemberAge = req.body.MemberAge;

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

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

module.exports = memberRoutes;

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 tasks 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');
memberRoute = require('./routes/member.route');

mongoose.Promise = global.Promise;
mongoose.connect(config.DB, { useUnifiedTopology: true, 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('/members', memberRoute);

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.

nodemon server
[nodemon] 2.0.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node server.js`
Listening on port 4000
Database is connected

Step 16: 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 member. 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.

 

MongoDB database

Step 17: Angular 9 display data

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

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

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

<table class="table table-hover">
  <thead>
  <tr>
      <td>Member Name</td>
      <td>Member Bio</td>
      <td>Member Age</td>
      <td colspan="2">Actions</td>
  </tr>
  </thead>

  <tbody>
      <tr *ngFor="let member of members; let i = index">
          <td>{{ member.MemberName }}</td>
          <td>{{ member.MemberBio }}</td>
          <td>{{ member.MemberAge }}</td>
          <td><a [routerLink]="['/edit', member._id]" class="btn btn-primary">Edit</a></td>
          <td><a [routerLink]="" class="btn btn-danger">Delete</a></td>
      </tr>
  </tbody>
</table>

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

// member.service.ts

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

In the above getMembers() 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 member.service.ts file and Member.ts file inside a member-get.component.ts file.

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

// member-get.component.ts

import { Component, OnInit } from '@angular/core';
import Member from '../Member';
import { MemberService } from '../member.service';

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

  members: Member[];
  constructor(private ms: MemberService) { }

  ngOnInit() {
    this.ms
      .getMembers()
      .subscribe((data: Member[]) => {
        this.members = data;
      });
  }

}

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

 

Step 18: Angular 9 Edit and update data

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

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

So first, add the following code inside the member-edit.component.ts file.

// member-edit.component.ts

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

@Component({
  selector: 'app-member-edit',
  templateUrl: './member-edit.component.html',
  styleUrls: ['./member-edit.component.css']
})
export class MemberEditComponent implements OnInit {
  angForm: FormGroup;
  member: any = {};

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

  createForm() {
    this.angForm = this.fb.group({
      MemberName: ['', Validators.required],
      MemberBio: ['', Validators.required],
      MemberAge: ['', Validators.required]
    });
  }


  ngOnInit(): void {
    this.route.params.subscribe(params => {
      this.ms.editMember(params[`id`]).subscribe(res => {
        this.member = res;
      });
    });
  }
}

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

Now, inside the member.service.ts file, we need to code the editMember() function to send an HTTP request.

// member.service.ts

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

Now, we need to add the form code inside the member-edit.component.html file.

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

<div class="card">
  <div class="card-body">
    <form [formGroup]="angForm" novalidate>
      <div class="form-group">
        <label class="col-md-4">Member Name</label>
        <input type="text" class="form-control" 
        formControlName="MemberName" 
          #MemberName
          [(ngModel)] = "member.MemberName"/>
      </div>
      <div *ngIf="angForm.controls['MemberName'].invalid && (angForm.controls['MemberName'].dirty || angForm.controls['MemberName'].touched)" class="alert alert-danger">
        <div *ngIf="angForm.controls['MemberName'].errors.required">
          Member Name is required.
        </div>
      </div>
      <div class="form-group">
        <label class="col-md-4">Member Bio </label>
        <textarea class="form-control" rows = 7 cols = "5"
        formControlName="MemberBio" 
        #MemberBio
        [(ngModel)] = "member.MemberBio"></textarea>
      </div>
      <div *ngIf="angForm.controls['MemberBio'].invalid && (angForm.controls['MemberBio'].dirty || angForm.controls['MemberBio'].touched)" class="alert alert-danger">
        <div *ngIf="angForm.controls['MemberBio'].errors.required">
          Member Bio is required.
        </div>
      </div>
      <div class="form-group">
        <label class="col-md-4">Member Age</label>
        <input type="text" class="form-control" 
        formControlName="MemberAge" 
        #MemberAge
        [(ngModel)] = "member.MemberAge"/>
      </div>
      <div *ngIf="angForm.controls['MemberAge'].invalid && (angForm.controls['MemberAge'].dirty || angForm.controls['MemberAge'].touched)" class="alert alert-danger">
        <div *ngIf="angForm.controls['MemberAge'].errors.required">
          Member Age is required.
        </div>
      </div>
      <div class="form-group">
        <button type="submit" class="btn btn-primary"
        [disabled]="angForm.pristine || angForm.invalid"
        (click) = "updateMember(MemberName.value, MemberBio.value, MemberAge.value)">Update Member</button>
      </div>
    </form>
  </div>
</div>

Save the file and go to a listing page and click on the edit button and you will see the member-edit component with populated form fields from the Database concerning that row.

Now, let’s update the data into the Database concerning their _id.

Inside the member.service.ts file, we need to write a function that updates the form fields data. See the following code.

// member.service.ts

updateMember(MemberName, MemberBio, MemberAge, id) {
    const obj = {
      MemberName,
      MemberBio,
      MemberAge
    };
    this
      .http
      .post(`${this.uri}/update/${id}`, obj)
      .subscribe(res => console.log('Done'));
  }

Okay, now write the updateMember() function inside member-edit.component.ts file.

// edit-member.component.ts

updateMember(MemberName, MemberBio, MemberAge) {
    this.route.params.subscribe(params => {
      this.ms.updateMember(MemberName, MemberBio, MemberAge, params.id);
      this.router.navigate(['members']);
    });
  }

In the above method, we get the form values from the HTML form and sends the PUT request to a backend server with the updated values, and at the server-side, the update function will grab the values and update the values inside the MongoDB database.

Save the file, and you can update the data into the Database.

Step 19: Angular 9 Delete form data.

I have already written the edit and update service functions to make API calls.

So up until now, we have completed the Create, Read, Update task of this Angular 9 Tutorial CRUD post.

Now, take a look at how to Delete or remove the data from the Database.

Now, we have to define the click event on the delete button inside the member-get.component.html file. See the following code.

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

 <tbody>
      <tr *ngFor="let member of members; let i = index">
          <td>{{ member.MemberName }}</td>
          <td>{{ member.MemberBio }}</td>
          <td>{{ member.MemberAge }}</td>
          <td><a [routerLink]="['/edit', member._id]" class="btn btn-primary">Edit</a></td>
         <td><a (click) = "deleteMember(member._id, i)" class="btn btn-danger">Delete</a>
      </tr>
  </tbody>

Now, write the deleteMember() function inside the member-get.component.ts file.

<!-- member-get.component.ts

deleteMember(id, index) {
    this.ms.deleteMember(id).subscribe(res => {
      this.members.splice(index, 1);
    });
}

The above method will send the ID to the server to delete the specific row based on that id from the Database. On the frontend, we are using the Javascript Splice function to remove the data from an Angular application using the array index.

Now, create deleteMember() function inside the member.service.ts file. We will send the id in this AJAX request to delete the data from the backend server.

// member.service.ts

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

Finally, we have completed the delete functionality.

So, in this extensive tutorial, we have completed the CRUD Functionality in Angular 9. I have also put this code on Github. Please check out that code as well.

If you have any doubts or questions in the Angular 9 CRUD Example, then ask in a comment below. If I were busy, then some will surely reply to your question.

ANGULAR 9 CRUD CODE

9 Comments
  1. Daniel says

    Thanks a lot. I’m a JavaScript developer making the switch from React to Angular. Really helpful.

  2. Samyr Rakoto says

    Hi thank you very much for this tutorial, it is well explained and the examples are good and easy to practice. All the best!

  3. mohamed anis says

    thank you for the great tutorial.

  4. Raza usmani says

    Hi, greate tutorial no doubt. I have followed all your steps to replicate the project. However I am stucked at step 15, unable to post data / create member. The following error occuring:

    Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: ‘member’

    can you help me resolve this issue, It seems that URL generated is not correct.

  5. LK says

    This tutorial is great. Thanks for your sharing.

  6. Benjou says

    Hi, great tutorial.
    Just, ‘port’ variable is missing on the express listen() function in server.js :

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

  7. Joe Puccia says

    Great tutorial. I’m just learning Angular and this helped a lot.

    After performing an update and it navigates back to the Members URL the old values are displayed. I have to perform a browser refresh to see the updated values. Is there a way for the navigate to force a refresh?

  8. Pete says

    Great tutorial. Been working through this tutorial and making some changes in the process so I can understand clearly what is happening in each step. I am using MySQL and sequelize for the ORM instead of MondoDB and changed the project to be a task listing rather than members. I am stuck on the “edit” of the data. It doesn’t seem to bring the data into the form. Where does the “member” object get created and passed back to the form e.g. [(ngModel)] = “member.MemberAge” ? I am a complete noob with Angular so I know I am biting off quite a bit by making the changes I have but somehow the object with the data isn’t either getting created or passed to the form. Can you point me to how that happens in your code? Thanks!

    1. Pete says

      Figured this out. Sequelize was returning a promise (read the docs!) which I failed to understand. Working as designed now.

Leave A Reply

Your email address will not be published.

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