Angular 8 CRUD Example

Angular 8 CRUD Example thumbnail
210K
By Dhiraj 30 June, 2019

In this tutorial, we will be developing an Angular 8 application and perform CRUD operation on a user entity. We will be developing a full stack app with REST API integration. We will have a login, list user, add and edit user component and based on the routing configurations these pages will be served. We will have HTTP interceptor to intercept our HTTP request and inject JWT token in the header. The backend server code is implemented using Spring Boot.

Angular 8 Release Highlights

With the release of Angular 8, there are many new features that has been introduced such as Ivy Preview, Web Workers, Lazy Loading, Differential Loading, etc. The new version requires TypeScript 3.4+ and Node 12+.

Ivy is a new rendering engine that will produce smaller bundle sizes that is more performance optimised. To start a new project with Ivy enabled, use the --enable-ivy flag with the ng new command.

ng new shiny-ivy-app --enable-ivy

Web Workers allow you to run CPU intensive computations in a background thread, freeing the main thread to update the user interface.

Differential Loading is used to build separate bundles to legacy browsers with related necessary JS bundles and polyfills by default.

Angular 8 Environment Setup

As Angular 8 requires Node 12+, let us download Node 12.5 from the official website and install it. Once Node 12+ is installed, we have NPM 6.9 available.

Now, it's time to update @angular/cli. Either you can uninstall the previous version of it and install it again to have the latest version or else try ng update.

npm install -g @angular/cli  #install latest version of angular/cli
ng update @angular/cli  #Update to latest version
npm uninstall -g @angular/cli    #Uninstall the previous version
npm cache clean

If you remember, we have already created an Angular 7 CRUD application and Angular 6 CRUD application in our last post. Let us try to upgrade Angular 7 app to Angular 8 in this section. In the below section, we will start building an Angular 8 app from scratch.

Traverse to the working workspace of Angular 7 project and run below update command to upgrade the exisitng application to Angular 8. As our application was a simple app, there may not be any breaking changes.

Angular 8 CLI Project Generation

Let us first generate a sample Angular 8 project through angular/cli and then we will modify it to create a full stack app to perform CRUD operations - list, add, edit and delete user.

Execute below commands to generate an Angular 8 project with CLI. Here, we to try out Ivy, we will be generating the new project through the switch enable-ivy

ng new angular8-demo --enable-ivy
cd angular8-demo/
ng serve

Now, you can actually import this project in your IDE for further changes.

angular8-project-strcture

Angular 8 Components

We will have different components for login, add user, edit user and list user. Let us generate these components with CLI commands first.

ng g component login
ng g component user/list-user
ng g component user/add-user
ng g component user/edit-user

Once these components are generated, let us start implementing each components step by step.First, let us remove the default content of app.component.html.

Login Component

The login component has a form to take username and password as input. It uses reactive forms for validation. Username and password is validated from the DB. The server is a spring boot app with JWT authentication. It's source code can be found at github here and the complete implementation of this server project can be found here.

login.component.html
<div class="row">

  <div class="col-md-6 login-container">
    <h2 style="margin: auto">Login </h2>
    <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
      <div class="form-group">
        <label for="username">UserName:</label>
        <input type="text" class="form-control" formControlName="username" id="username" autocomplete="off">
        <div class="error" *ngIf="loginForm.controls['username'].hasError('required') && loginForm.controls['username'].touched">Username is required</div>
      </div>
      <div class="form-group">
        <label for="pwd">Password:</label>
        <input type="password" class="form-control" formControlName="password" id="pwd" autocomplete="off">
        <div class="error" *ngIf="loginForm.controls['password'].hasError('required') && loginForm.controls['password'].touched">Password is required</div>
      </div>
      <button class="btn btn-success" [disabled]="loginForm.invalid">Login</button>
      <div *ngIf="invalidLogin" class="error">
        <div>Invalid credentials.</div>
      </div>
    </form>
  </div>
</div>

Post login, the token will be saved in the local storage.

login.component.ts
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {Router} from "@angular/router";
import {ApiService} from "../service/api.service";

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

  loginForm: FormGroup;
  invalidLogin: boolean = false;
  constructor(private formBuilder: FormBuilder, private router: Router, private apiService: ApiService) { }

  onSubmit() {
    if (this.loginForm.invalid) {
      return;
    }
    const loginPayload = {
      username: this.loginForm.controls.username.value,
      password: this.loginForm.controls.password.value
    }
    this.apiService.login(loginPayload).subscribe(data => {
      debugger;
      if(data.status === 200) {
        window.localStorage.setItem('token', data.result.token);
        this.router.navigate(['list-user']);
      }else {
        this.invalidLogin = true;
        alert(data.message);
      }
    });
  }

  ngOnInit() {
    window.localStorage.removeItem('token');
    this.loginForm = this.formBuilder.group({
      username: ['', Validators.compose([Validators.required])],
      password: ['', Validators.required]
    });
  }

}

List User Component

Post login, list-user component will be rendered. This component renders the list of users and an option to perform CRUD operations. If there is no valid token found in the localstorage, the user will be redirected to login page again.

You can visit my another article to know Angular JWT authentication in detail.

list-user.component.html
<div class="col-md-6 user-container">
  <h2 style="margin: auto"> User Details</h2>
  <button class="btn btn-danger" style="width:100px" (click)="addUser()"> Add User</button>
  <table class="table table-striped">
    <thead>
    <tr>
      <th class="hidden">Id</th>
      <th>FirstName</th>
      <th>LastName</th>
      <th>UserName</th>
      <th>Age</th>
      <th>Salary</th>
    </tr>
    </thead>
    <tbody>
    <tr *ngFor="let user of users">
      <td class="hidden">{{user.id}}</td>
      <td>{{user.firstName}}</td>
      <td>{{user.lastName}}</td>
      <td>{{user.username}}</td>
      <td>{{user.age}}</td>
      <td>{{user.salary}}</td>
      <td><button class="btn btn-success" (click)="deleteUser(user)"> Delete</button>
        <button class="btn btn-success" (click)="editUser(user)" style="margin-left: 20px;"> Edit</button></td>
    </tr>
    </tbody>
  </table>
</div>
list-user.component.html
import { Component, OnInit , Inject} from '@angular/core';
import {Router} from "@angular/router";
import {User} from "../../model/user.model";
import {ApiService} from "../../service/api.service";

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

  users: User[];

  constructor(private router: Router, private apiService: ApiService) { }

  ngOnInit() {
    if(!window.localStorage.getItem('token')) {
      this.router.navigate(['login']);
      return;
    }
    this.apiService.getUsers()
      .subscribe( data => {
        this.users = data.result;
      });
  }

  deleteUser(user: User): void {
    this.apiService.deleteUser(user.id)
      .subscribe( data => {
        this.users = this.users.filter(u => u !== user);
      })
  };

  editUser(user: User): void {
    window.localStorage.removeItem("editUserId");
    window.localStorage.setItem("editUserId", user.id.toString());
    this.router.navigate(['edit-user']);
  };

  addUser(): void {
    this.router.navigate(['add-user']);
  };
}

Add User Component

Let us add new user to our DB with this component.

add-user.component.ts
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {Router} from "@angular/router";
import {ApiService} from "../../service/api.service";

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

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

  addForm: FormGroup;

  ngOnInit() {
    this.addForm = this.formBuilder.group({
      id: [],
      username: ['', Validators.required],
      password: ['', Validators.required],
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      age: ['', Validators.required],
      salary: ['', Validators.required]
    });

  }

  onSubmit() {
    this.apiService.createUser(this.addForm.value)
      .subscribe( data => {
        this.router.navigate(['list-user']);
      });
  }

}

add-user.component.html
<div class="col-md-6 user-container">
  <h2 class="text-center">Add User</h2>
  <form [formGroup]="addForm" (ngSubmit)="onSubmit()">
    <div class="form-group">
      <label for="username">User Name:</label>
      <input type="text" formControlName="username" placeholder="username" name="username" class="form-control" id="username">
    </div>

    <div class="form-group">
      <label for="password">Password:</label>
      <input type="password" formControlName="password" placeholder="password" name="password" class="form-control" id="password">
    </div>

    <div class="form-group">
      <label for="firstName">First Name:</label>
      <input formControlName="firstName" placeholder="First Name" name="firstName" class="form-control" id="firstName">
    </div>

    <div class="form-group">
      <label for="lastName">Last Name:</label>
      <input formControlName="lastName" placeholder="Last name" name="lastName" class="form-control" id="lastName">
    </div>

    <div class="form-group">
      <label for="age">Age:</label>
      <input type="number" formControlName="age" placeholder="age" name="age" class="form-control" id="age">
    </div>

    <div class="form-group">
      <label for="salary">Salary:</label>
      <input type="number" formControlName="salary" placeholder="salary" name="salary" class="form-control" id="salary">
    </div>

    <button class="btn btn-success">Add</button>
  </form>
</div>

Edit User Component

Whenever, edit button is clicked, the selected user id is stored in the local storage and inside ngOnInit(), an API call is made to fetch the user by user id and the form is auto-populated.

edit-user.component.html
<div class="col-md-6 user-container">
  <h2 class="text-center">Edit User</h2>
  <form [formGroup]="editForm" (ngSubmit)="onSubmit()">
    <div class="hidden">
      <input type="text" formControlName="id" placeholder="id" name="id" class="form-control" id="id">
    </div>
    <div class="form-group">
      <label for="username">User Name:</label>
      <input type="text" formControlName="username" placeholder="username" name="username" class="form-control" id="username" readonly="true">
    </div>

    <div class="form-group">
      <label for="firstName">First Name:</label>
      <input formControlName="firstName" placeholder="First Name" name="firstName" class="form-control" id="firstName">
    </div>

    <div class="form-group">
      <label for="lastName">Last Name:</label>
      <input formControlName="lastName" placeholder="Last name" name="lastName" class="form-control" id="lastName">
    </div>

    <div class="form-group">
      <label for="age">Age:</label>
      <input type="number" formControlName="age" placeholder="age" name="age" class="form-control" id="age">
    </div>

    <div class="form-group">
      <label for="salary">Salary:</label>
      <input type="number" formControlName="salary" placeholder="salary" name="salary" class="form-control" id="salary">
    </div>

    <button class="btn btn-success">Update</button>
  </form>
</div>
edit-user.component.ts
import { Component, OnInit , Inject} from '@angular/core';
import {Router} from "@angular/router";
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {first} from "rxjs/operators";
import {User} from "../../model/user.model";
import {ApiService} from "../../service/api.service";

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

  user: User;
  editForm: FormGroup;
  constructor(private formBuilder: FormBuilder,private router: Router, private apiService: ApiService) { }

  ngOnInit() {
    let userId = window.localStorage.getItem("editUserId");
    if(!userId) {
      alert("Invalid action.")
      this.router.navigate(['list-user']);
      return;
    }
    this.editForm = this.formBuilder.group({
      id: [''],
      username: ['', Validators.required],
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      age: ['', Validators.required],
      salary: ['', Validators.required]
    });
    this.apiService.getUserById(+userId)
      .subscribe( data => {
        this.editForm.setValue(data.result);
      });
  }

  onSubmit() {
    this.apiService.updateUser(this.editForm.value)
      .pipe(first())
      .subscribe(
        data => {
          if(data.status === 200) {
            alert('User updated successfully.');
            this.router.navigate(['list-user']);
          }else {
            alert(data.message);
          }
        },
        error => {
          alert(error);
        });
  }

}

Angular 8 API Service Implementation

Below is the service class that makes HTTP calls to perform the CRUD operations. It uses HttpClient from @angular/common/http

api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {User} from "../model/user.model";
import {Observable} from "rxjs/index";
import {ApiResponse} from "../model/api.response";

@Injectable()
export class ApiService {

  constructor(private http: HttpClient) { }
  baseUrl: string = 'http://localhost:8080/users/';

  login(loginPayload) : Observable<ApiResponse> {
    return this.http.post<ApiResponse>('http://localhost:8080/' + 'token/generate-token', loginPayload);
  }

  getUsers() : Observable<ApiResponse> {
    return this.http.get<ApiResponse>(this.baseUrl);
  }

  getUserById(id: number): Observable<ApiResponse> {
    return this.http.get<ApiResponse>(this.baseUrl + id);
  }

  createUser(user: User): Observable<ApiResponse> {
    return this.http.post<ApiResponse>(this.baseUrl, user);
  }

  updateUser(user: User): Observable<ApiResponse> {
    return this.http.put<ApiResponse>(this.baseUrl + user.id, user);
  }

  deleteUser(id: number): Observable<ApiResponse> {
    return this.http.delete<ApiResponse>(this.baseUrl + id);
  }
}

Angular 8 Routing

The routing implementation in Angular 8 is similar to previous versions. Below is the implementation of our routing module that is imported in our main module.

app.routing.ts
import { RouterModule, Routes } from '@angular/router';
import {LoginComponent} from "./login/login.component";
import {AddUserComponent} from "./user/add-user/add-user.component";
import {ListUserComponent} from "./user/list-user/list-user.component";
import {EditUserComponent} from "./user/edit-user/edit-user.component";

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: 'add-user', component: AddUserComponent },
  { path: 'list-user', component: ListUserComponent },
  { path: 'edit-user', component: EditUserComponent },
  {path : '', component : LoginComponent}
];

export const routing = RouterModule.forRoot(routes);

Angular 8 App Module

We have all the modules such as BrowserModule, ReactiveFormsModule, RoutingModule, etc are importd here and we have bootstrapped AppComponent which provides the starting point of our app.

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { ListUserComponent } from './user/list-user/list-user.component';
import { LoginComponent } from './login/login.component';
import { AddUserComponent } from './user/add-user/add-user.component';
import { EditUserComponent } from './user/edit-user/edit-user.component';
import {routing} from "./app.routing";
import {ReactiveFormsModule} from "@angular/forms";
import {ApiService} from "./service/api.service";
import {HTTP_INTERCEPTORS, HttpClientModule} from "@angular/common/http";
import {TokenInterceptor} from "./core/interceptor";

@NgModule({
  declarations: [
    AppComponent,
    ListUserComponent,
    LoginComponent,
    AddUserComponent,
    EditUserComponent
  ],
  imports: [
    BrowserModule,
    routing,
    ReactiveFormsModule,
    HttpClientModule
  ],
  providers: [ApiService, {provide: HTTP_INTERCEPTORS,
    useClass: TokenInterceptor,
    multi : true}],
  bootstrap: [AppComponent]
})
export class AppModule { }

Angular 8 HTTP Interceptor

The interceptor implements HttpInterceptor which intercepts all the HTTP request and token in the header for API authentication. At the time of login, there won't be any valid token present in the local cache hence, there is a condition check for the presence of token.

interceptor.ts
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import {Observable} from "rxjs/internal/Observable";
import {Injectable} from "@angular/core";

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  intercept(request: HttpRequest, next: HttpHandler): Observable> {
    let token = window.localStorage.getItem('token');
    if (token) {
      request = request.clone({
        setHeaders: {
          Authorization: 'Bearer ' + token
        }
      });
    }
    return next.handle(request);
  }
}

API Details

API Name  - Token Generation
URL - localhost:8080/token/generate-token
Method - POST
Header - Content-Type: application/json
Body -
{
	"username":"alex123",
	"password":"alex123"
}

Response :

{
    "status": 200,
    "message": "success",
    "result": {
        "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE",
        "username": "alex123"
    }
}

API Name  - List User
URL - http://localhost:8080/users
Method - Get
Header - Content-Type: application/json
    Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE
Response -
{
    "status": 200,
    "message": "User list fetched successfully.",
    "result": [
        {
            "id": 1,
            "firstName": "Devglan",
            "lastName": "Devglan",
            "username": "devglan",
            "salary": 3456,
            "age": 33
        },
        {
            "id": 2,
            "firstName": "Tom",
            "lastName": "Asr",
            "username": "tom234",
            "salary": 7823,
            "age": 23
        },
        {
            "id": 3,
            "firstName": "Adam",
            "lastName": "Psr",
            "username": "adam",
            "salary": 4234,
            "age": 45
        }
    ]
}

API Name  - Create User
URL - http://localhost:8080/users
Method - POST
Header - Content-Type: application/json
    Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE
Body -
{
	"username":"test",
	"password":"test",
	"firstName":"test",
	"lastName":"test",
	"age":23,
	"salary":12345
}

Response -

{
    "status": 200,
    "message": "User saved successfully.",
    "result": {
        "id": 4,
        "firstName": "test",
        "lastName": "test"",
        "username": "test",
        "salary": 12345,
        "age": 23
    }
}


API Name  - Update User
URL - http://localhost:8080/users/4
Method - PUT
Header - Content-Type: application/json
    Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE
Body -
{
	"username":"test1",
	"password":"test1",
	"firstName":"test1",
	"lastName":"test1",
	"age":24,
	"salary":12345
}

Response -

{
    "status": 200,
    "message": "User updated successfully.",
    "result": {
        "id": 0,
        "firstName": "test1",
        "lastName": "test1",
        "username": "test1",
        "password": "test1",
        "age": 24,
        "salary": 12345
    }
}

API Name  - Delete User
URL - http://localhost:8080/users/4
Method - DELETE
Header - Content-Type: application/json
    Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGV4MTIzIiwic2NvcGVzIjpbeyJhdXRob3JpdHkiOiJST0xFX0FETUlOIn1dLCJpc3MiOiJodHRwOi8vZGV2Z2xhbi5jb20iLCJpYXQiOjE1NDEwNjIzOTMsImV4cCI6MTU0MTA4MDM5M30.DMoB5kv72X7Jf-U5APdjK3UUcGomA9NuJj6XGxmLyqE

Response -
{
    "status": 200,
    "message": "User deleted successfully.",
    "result": null
}

Testing the App

Login angular8-login List User angular8-list-user Add User angular8-add-user Edit User angular8-edit-user

Conclusion

In this tutorial, we developed an Angular 8 application and implemented different CRUD operations with actual calls to REST services. The source code for this application can be found here on github.

Share

If You Appreciate This, You Can Consider:

We are thankful for your never ending support.

About The Author

author-image
A technology savvy professional with an exceptional capacity to analyze, solve problems and multi-task. Technical expertise in highly scalable distributed systems, self-healing systems, and service-oriented architecture. Technical Skills: Java/J2EE, Spring, Hibernate, Reactive Programming, Microservices, Hystrix, Rest APIs, Java 8, Kafka, Kibana, Elasticsearch, etc.

Further Reading on Angular JS