Create a CRUD Application with Inline Table Editing in Angular 14 +

Chadwin Deysel - Dec 1 '22 - - Dev Community

Introduction

In this post, I will show you how to create a CRUD application in Angular, that allows you to create, update and delete records from the table itself.

This project is suited for beginners.

You can find the repository for the project on my [GitHub] by clicking here.

Please note, this application will not be integrated with a database and is for demonstration purposes only. So let’s get started.

Initialize the project setup.

Firstly, we need to ensure that you have Nodejs, NPM and Angular installed. You can check this by opening a new terminal and running the following commands:

node -v
Enter fullscreen mode Exit fullscreen mode
npm -v
Enter fullscreen mode Exit fullscreen mode
ng version
Enter fullscreen mode Exit fullscreen mode

If you haven’t yet installed any of these, I would recommend you check out this guide.

Now let’s create our application. In your terminal, run the following command.

ng new inline-table-editor
Enter fullscreen mode Exit fullscreen mode

Then, navigate into your project and open up the file in your code editor of choice.

Now that the application has been created, we’ll need to install and set up the UI library which in this case is going to Bootstrap. Install NGXBootstrap:

ng add ngx-bootstrap
Enter fullscreen mode Exit fullscreen mode

Next, we’ll need to add the Bootstrap & Bootstrap Icons CSS libraries into the application. To do so, navigate to the index.html page, and in the <head> section add the following code:

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css">

<!-- Bootstrap Icons CSS --> 
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
Enter fullscreen mode Exit fullscreen mode

With that, the project is ready to go.

Create the Users table.

Let’s get started by creating the user's table. First, we’ll need to define our data model. In the root of the src folder, create a new folder names models. And add a new file titled: “Users.ts”. In the Users file add the following code to define the Users interface:

export interface User {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
}
Enter fullscreen mode Exit fullscreen mode

Next, we’ll seed some demo data into the table. To generate some demo data, you can checkout JSON Generator. Once you’ve opened the window, replace the code in the left tab with the following code and hit generate.

[
   '{{repeat(8)}}',
  {
    id: '{{objectId()}}',
    firstName: '{{firstName()}}',
    lastName: '{{surname()}}',
    email: '{{email()}}',
  }
]
Enter fullscreen mode Exit fullscreen mode

This will generate 8 random objects that can then be copied to your clipboard. Then navigate back to our application and in app.component.ts file, create a new variable and paste your mock data.

user: User[] = [
    // paste your generated content here.
]
Enter fullscreen mode Exit fullscreen mode

It’s worth pointing out that in real-life applications, the data would be stored in a database and retrieved once needed using an API.

Now with that completed, navigate to the app.component.html file and replace all the existing code with the following to create the table.

<div class="container">
    <table class="table">
        <thead>
        <tr>
          <th scope="col">Id</th>
          <th scope="col">First Name</th>
          <th scope="col">Last Name</th>
          <th scope="col">Email Address</th>
          <th>
            <button
              type="button"
              title="Add New User"
              class="btn btn-primary"
            >
              <i class="bi bi-plus"></i>
            </button>
          </th>
        </tr>
      </thead>

            <tbody>
        <ng-container *ngFor="let user of users; let i = index">
          <tr>
            <td>{{ user.id }}</td>
            <td>{{ user.firstName }}</td>
            <td>{{ user.lastName }}</td>
            <td>{{ user.email }}</td>
            <td>
              <button
                class="btn btn-danger btn-sm mx-1"
                title="Delete User"
                type="button"
              >
                <i class="bi bi-trash"></i>
              </button>
            </td>
          </tr>
        </ng-container>
      </tbody>
    </table>
</div>
Enter fullscreen mode Exit fullscreen mode

Once completed, run the application and navigate to http://localhost:4200, view your table with the demo data inside of it.

ng serve
Enter fullscreen mode Exit fullscreen mode

Updating an Existing User

To perform updates to the data, we’ll be making use of the Reactive Forms Module that’s built in angular. Firstly, add the ReactiveFormsModule to you application in the app.module.ts file.

imports: [
    // Existing Modules
    ReactiveFormsModule,
], 
Enter fullscreen mode Exit fullscreen mode

Next we’ll need to add a constructor to our app.component.ts file and inject the Form Builder, which is used to build our reactive forms.

constructor(
    private fb: FormBuilder
) { }
Enter fullscreen mode Exit fullscreen mode

Now, we’ll create our form with the needed fields and validation requirements.

form = this.fb.group({
    firstName: ['', [Validators.required]],
    lastName: ['', [Validators.required]],
    email: ['', [Validators.required, Validators.email]]
 });
Enter fullscreen mode Exit fullscreen mode

I’m planning on creating more content based on the Reactive Forms Module. Sign up to my newsletter for my latest updates.

With this application, you can update any user by just clicking on them. However, we want to make sure that the user is only able to make changes to once user at a time. To accomplish that, we’ll create a property, userSelected and a method called selectUser() to select a user to select the user that is too be updated.

userSelected: User = {} as User;

selectUser(user: User) {
        // check if userSelected is empty, before assigning a selected user
    if(Object.keys(this.userSelected).length === 0) {
      this.userSelected = user;

      this.form.patchValue({
        firstName: user.firstName,
        lastName: user.lastName,
        email: user.email
      })
    }
  }
Enter fullscreen mode Exit fullscreen mode

Next, we’ll need to let the UI respond to us selecting a user. For this I want the background color of the row to change and replace the table data with inputs. To achieve this, replace the code in the <tbody> with the following.

<tbody>
  <ng-container *ngFor="let user of users; let i = index">
    <tr
            *ngIf="userSelected != user"
            (click)="selectUser(user)"
        >
      <td>{{ user.id }}</td>
      <td>{{ user.firstName }}</td>
      <td>{{ user.lastName }}</td>
      <td>{{ user.email }}</td>
      <td>
        <button
          class="btn btn-danger btn-sm mx-1"
          title="Delete User"
          type="button"
        >
          <i class="bi bi-trash"></i>
        </button>
      </td>
    </tr>

        <tr *ngIf="userSelected == user" class="table-primary">
      <td>{{ user.id }}</td>
      <td>
          <input
            type="text"
            class="form-control"
            formControlName="firstName"
            [value]="user.firstName"
            title="First Name"
            />
      </td>
      <td>
        <input
          type="text"
          class="form-control"
          formControlName="lastName"
          [value]="user.lastName"
          title="Last Name"
          />
      </td>
      <td>
        <input
          type="text"
          class="form-control"
          formControlName="email"
          [value]="user.email"
          title="Email Address"
          />
      </td>
      <td>             
        <button
            class="btn btn-primary btn-sm mx-1"
            title="update"
            type="submit"
            [disabled]="form.invalid"
          >
            <i class="bi bi-lightning"></i>
          </button>

          <button
            class="btn btn-danger btn-sm mx-1"
            title="Cancel Changes"
            type="button"
          >
            <i class="bi bi-x-lg"></i>
          </button>
        </td>
    </tr>
  </ng-container>
</tbody>
Enter fullscreen mode Exit fullscreen mode

To summarize this code, it checks if the user selected is the user that is in the row of the table. If so, we change the background color of the row and add in the form inputs needed. It’s important to note that this code the form above will not work. To do that, we’ll need to wrap our table in a <form> tag.

<form [formGroup]="form" (ngSubmit)="update()">
    <table class="table">

    </table>
</form>
Enter fullscreen mode Exit fullscreen mode

Now let’s create the update() to update the user.

update() {
      // gets the index of the user we need to update
    let index = this.users.map(u => u.id).indexOf(this.userSelected.id);

        // updates the user at the index selected
    this.users[index] = {
      id: this.userSelected.id,
      firstName: this.form.value.firstName!,
      lastName: this.form.value.lastName!,
      email: this.form.value.email!
    };

        // clean up
        this.userSelected = {} as User;
    this.form.reset();  
}
Enter fullscreen mode Exit fullscreen mode

Finally, we’ll need the “Cancel Changes” button to undo the changes and reset the UI.

<!-- Cancel Changes Button in app.component.html -->
<button
  class="btn btn-danger btn-sm mx-1"
  title="Cancel Changes"
  type="button"
    (click)="cancel()"
>
    <i class="bi bi-x-lg"></i>
</button>
Enter fullscreen mode Exit fullscreen mode
// app.component.ts
cancel() {
    // clears the user selected
    this.userSelected = {} as User;

    // resets the form
  this.form.reset();
}
Enter fullscreen mode Exit fullscreen mode

Create a new User

To create a new user, we’ll use a lot of the same methods and functionality that we’ve already built in. Let’s start by adding the isEditing property. This property will distinguish between if we’re currently editing an existing user or adding a new one. Add the isEditing variable to the top of the app.component.ts.

isEditing: boolean = false
Enter fullscreen mode Exit fullscreen mode

Next, let’s make changes to our existing code to update the functionality. Replace the existing methods in the app.component.ts with the code below.

selectUser(user: User) {
  if(Object.keys(this.userSelected).length === 0) {
    this.userSelected = user;
    this.isEditing = true

    this.form.patchValue({
      firstName: user.firstName,
      lastName: user.lastName,
      email: user.email
    })
  }
}

update() {
  if(!this.isEditing) {
    this.users[0] = {
      id: this.generateId(),
      firstName: this.form.value.firstName!,
      lastName: this.form.value.lastName!,
      email: this.form.value.email!
    }
  }   
  else {
    let index = this.users.map(u => u.id).indexOf(this.userSelected.id);

    this.users[index] = {
      id: this.userSelected.id,
      firstName: this.form.value.firstName!,
      lastName: this.form.value.lastName!,
      email: this.form.value.email!
    };
  }

  // clean up
  this.userSelected = {} as User;
  this.isEditing = false 
  this.form.reset();   
}

cancel() {
  if(!this.isEditing && confirm('All unsaved changes will be removed. Are you sure you want to cancel?')) {
        // removes the user that was added
    this.users.splice(0, 1);
  }

  this.userSelected = {} as User;
  this.isEditing = false
  this.form.reset();
}
Enter fullscreen mode Exit fullscreen mode

We’ll also need to add another method to generate an ID for the user. In a real world scenario, this would be generated by the backend or database.

// Generates a random string of characters.
generateId() {
  return (Math.random() + 1).toString(36).substring(4) + (Math.random() + 1).toString(36).substring(4) 
}
Enter fullscreen mode Exit fullscreen mode

Finally, let’s create the addUser() to set add our new user to the frontend.

<!-- Add New User button, app.component.html -->
<button
  type="button"
  title="Add New"
  class="btn btn-primary"
  (click)="addUser()"
>
  <i class="bi bi-plus"></i>
</button>
Enter fullscreen mode Exit fullscreen mode
// app.component.ts
addUser() {
  this.users.unshift({
    id: '-',
    firstName: '',
    lastName: '',
    email: ''
  })
  this.userSelected = this.users[0];
}
Enter fullscreen mode Exit fullscreen mode

Deleting a User

To finalize all the CRUD functionality, we’ll need to create a way to delete a user.

<!-- Delete User button, app.component.html -->
<button
  class="btn btn-danger btn-sm mx-1"
  title="delete"
  type="button"
  (click)="deleteUser(i)"
>
  <i class="bi bi-trash"></i>
</button>
Enter fullscreen mode Exit fullscreen mode
// app.component.ts
deleteUser(index: number) {
  if(confirm('Are you sure you want to delete this user?')) {
    this.users.splice(index, 1);
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

I hope you found this post useful.

Be sure to check me out on Twitter for more Angular and development tips. Thanks for reading and have a great day! 😄

. . . . . . . . . . . . . . . . .