Skip to main content

Angular 8 CRUD Example

Angular 8 CRUD Example


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"></td>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td></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

Comments

Popular posts from this blog

Angular Architecture

Angular Architecture is divided into different parts like Component, Template, Directive, Service, and Module. Before you start to develop your application, you should have knowledge about the purpose of each part of the application. So that you can make your application smoother and faster. Below is the figure of angular architecture. Angular Architecture In this chapter, we will see each term in detail. Module Angular Applications are modular and Angular has its own modularity system called NgModule. Every Angular application has at least one class with a @NgModule decorator, it is the root module, conventionally named as AppModule. To use any component into an application you need to declare it into the related module. Angular Module is class with a @NgModule decorator. Code Snippet of Angular Module The most important properties of @NgModule are : declarations:  declaration property contains a li

How to start a new Angular 9 Project in a few easy steps

How to start a new Angular 9 Project in a few easy steps In order to successfully create and run an Angular project locally, the following three software must be installed on your machine. VScode  [ Link ] Visual Studio Code is a source code editor developed by Microsoft for Windows, Linux, and macOS. It includes support for debugging, embedded Git control and GitHub, syntax highlighting, intelligent code completion, snippets, and code refactoring. Node  [ Link ] Node.js is an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside of a browser. npm  [ Link ] npm is the world’s largest software registry. Open-source developers from every continent use npm to share and borrow packages and many organizations use npm to manage private development as well. 1 — Install Software 1.1 — Install Visual Studio Code In order to write code, we must use an IDE, and Microsoft has developed an open-source and cross-platform IDE that can

Benefits of Learning Angular

Benefits of Learning Angular There’s a lot of platforms and tools out there, so what makes Angular so special? What sort of advantages does it bring to the table, that it’s so important for developers to know its ins and outs? Here are some of Angular’s most impressive advantages: Less Coding The best code is short but effective, getting the most functionality with expending the least effort. Fortunately, Angular supports Model View Controlling architecture (MVC). All the developer needs to do is split their code to fit into the MVC architecture, and Angular takes care of the rest! No worries about having to do the MVC pipeline. Ease of Integration There’s a host of frameworks out there that have Angular already built into them, including Kendo UI, Wijmo, and Ionic, to name a few. That just goes to show that Angular plays well with others. Single Page Applications Supported A Single Page Applications is defined as a web application that loads a single HTML page. The page