How to Show a Modal Dialog in Angular
Tags: angular, typescript, frontend, programming
Published: 2023-11-03 19:02:18

Using modal dialogs in web applications is fairly common. Whether you want to edit a record in a table without navigating to a different page, look up some data, or just show a warning message to your users, using a modal dialog may be a great user experience. Unfortunately, doing that is not very easy in Angular without a third party library. It requires writing some non trivial code and understanding of the internal mechanisms of the Angular framework. That is the reason why we developed a library at Developer Partners for showing modal dialogs in Angular. We are going to use our library in this article.

## 1. Install the Library
We will have to install our [@developer-partners/ngx-modal-dialog](https://www.npmjs.com/package/@developer-partners/ngx-modal-dialog) library via NPM and include that in our Angular modules to be able to use it. Here is the NPM command to install it:
`npm install @developer-partners/ngx-modal-dialog`

Next, we have to include it in the Angular modules where we have to use modal dialogs. Our demo application is very small, it has only one module called `AppModule`. We have to include `ModalModule` from the library in the imports array of our `AppModule` Angular module:

```
// Import ModalModule
import { ModalModule } from '@developer-partners/ngx-modal-dialog';

@NgModule({
  declarations: [
    // Declarations of your app.
  ],
  imports: [
    // Import other Angular modules

    // Add ModalModule to your module imports.
    ModalModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

We are all set. We can start using the modal dialog component in our application.

## 2. Show a Modal Dialog
As mentioned before, we are going to show our modal dialog when we want to add or edit a book in our list of books. Let's first create a component where we are going to show the list of books. We are going to create a new Angular component and call it `BookListComponent`. This is the content of the `BookListComponent` Typescript class:

```
import { Component } from "@angular/core";
import { ModalService } from "@developer-partners/ngx-modal-dialog";
import { Book } from "src/app/shared/models/book";

@Component({
    selector: 'app-book-list',
    templateUrl: './book-list.component.html'
})
export class BookListComponent {
    public books?: Book[] = [
        {
            id: 1,
            title: 'The Great Gatsby'
        },
        {
            id: 2,
            title: 'One Hundred Years of Solitude'
        },
        {
            id: 3,
            title: 'War and Peace'
        },
        {
            id: 4,
            title: 'Pride and Prejudice'
        },
        {
            id: 5,
            title: 'To Kill a Mockingbird'
        }
    ];

    constructor(private readonly _modalService: ModalService) {

    }
}
```

Our `BookListComponent` class uses a hardcoded list of books as the data to show in our table for simplicity. It also needs the `ModalService` Angular service from the `ngx-modal-dialog` library to be able to show the modal dialogs that is why we injected it into our constructor.

Next, we will create the HTML file for `BookListComponent` where we will show the list of books in an HTML table:

```
<div>
    <button type="button" class="btn btn-primary mb-3" (click)="createBook()">
        Add New Book
    </button>

    <table class="table">
        <thead>
            <tr>
                <th scope="col">ID</th>
                <th scope="col">Title</th>
                <th scope="col">Actions</th>
            </tr>
        </thead>

        <tbody>
            <tr *ngFor="let book of books">
                <td>
                    {{book.id}}
                </td>
                <td>
                    {{book.title}}
                </td>
            </tr>
        </tbody>
    </table>
</div>
```

The HTML code above is pretty simple. It show the list of books in a table. It also has a button called "Add New Book". This is what the UI looks like so far:


![UI of the table with books](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lex3id03g1xmyny8pjxc.png)

When the "Add New Book" button is clicked, it calls the `createBook` function in our Typescript class. The `createBook` function shows the modal dialog for adding a new book to our table. This is the Typescript code of the `createBook` function:

```
constructor(private readonly _modalService: ModalService) {

}

public createBook(): void {
    this._modalService.show<Book>(CreateEditBookComponent, {
        title: 'Create Book'
    }).result()
        .subscribe(newBook => {
            this.books?.push(newBook);
        })
}
```

The `createBook` function shows the modal dialog by calling the `show` function of the `ModalService` class. The `show` function takes 2 parameters: the class of the Angular component to show inside the modal dialog and the options of modal dialog which are the settings of the dialog such as the title, size, position, and a few other things. The modal dialog needs an Angular component to show in its body. Without that component, our modal dialog would be just an empty panel overlaying the screen. That component is `CreateEditBookComponent` which is the first parameter of the show function in the code above. 

The `CreateEditBookComponent` Angular component  is going to be responsible for both adding and editing books. We will start by working on the code for adding new books. Here is the HTML code of the `CreateEditBookComponent` which has only a few fields for entering the ID and title of the book we want to create and has buttons for closing the dialog and saving the data:

```
<form (ngSubmit)="saveData()">
    <div class="mb-3">
        <label for="book-id" class="form-label">
            ID
        </label>
        <input id="book-id"
               name="id"
               type="number"
               min="0"
               class="form-control"
               required
               [(ngModel)]="book.id" />
    </div>

    <div class="mb-3">
        <label for="book-title" class="form-label">
            Title
        </label>
        <input id="book-title"
               name="title"
               class="form-control"
               required
               [(ngModel)]="book.title" />
    </div>

    <div class="text-center">
        <button type="button"
                class="btn btn-secondary"
                (click)="cancel()">
            Cancel
        </button>

        <button type="submit" class="btn btn-primary ms-2">
            Save
        </button>
    </div>
</form>
```

This is what the modal dialog UI looks like:

![UI of the Create Book modal dialog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0t6ws4ikjyp0hmo2ty8u.png)

Here is the Typescript code of the `CreateEditBookComponent` component:

```
import { Component } from "@angular/core";
import { ModalReference } from "@developer-partners/ngx-modal-dialog";
import { Book } from "src/app/shared/models/book";

@Component({
    templateUrl: './create-edit-book.component.html'
})
export class CreateEditBookComponent {
    public book: Book = {};

    constructor(private readonly _modalReference: ModalReference<Book>) {
        if (this._modalReference.config.model) {
            let copy = { ...this._modalReference.config.model };
            this.book = copy;
        }
    }

    public cancel(): void {
        this._modalReference.cancel();
    }

    public saveData(): void {
        this._modalReference.closeSuccess(this.book);
    }
}
```

The `CreateEditBookComponent` component uses an Angular service called `ModalReference` from the [ngx-modal-dialog](https://www.npmjs.com/package/@developer-partners/ngx-modal-dialog) library. We use that service for interacting with the modal dialog where our component is placed such as closing the modal or subscribing to its events. We simply close the modal dialog in the `cancel` function in the screenshot above. We call the `cancel` function when the "Cancel" button from HTML is clicked. When we click the "Save" button in the HTML code, it submits the form which calls the `saveData` function. In the `saveData` function, we close the modal dialog just like in the `cancel` function but we also pass the book property to it that contains the data for adding a new book to our list. 

The `ModalReference` service is a generic type. When we call the `closeSuccess` function of the `ModalReference` service, we have to pass an object of the same type as its generic parameter. In our case, it's a Typescript interface called `Book`. The parameter that we pass to the `closeSuccess` function is passed back to the component that created the modal dialog by calling the show function of the `ModalService` class.

```
// Code from BookListComponent.

public createBook(): void {
    this._modalService.show<Book>(CreateEditBookComponent, {
        title: 'Create Book'
    }).result()
        .subscribe(newBook => {
            this.books?.push(newBook);
        })
}
```

When the call the `closeSuccess` function of the `ModalReference` service, it closes the modal dialog and triggers an `RxJS` event passing the newly create book to the subscribers of that event. In the screenshot above, the `newBook` parameter of our callback function is the newly created book that we received from the the modal dialog, so we simply add it to our books array to show it in the UI. 

## 3. Passing Data to Modal Dialogs
There are some case that you need to pass some data to modal dialogs. For example, if we want to edit a book from the list, we can pass the book record that we want to edit to the modal dialog to have the initial data that we want to modify. Let's start by adding a button to the table rows for editing the row data:

```
<div>
    <button type="button"
            class="btn btn-primary mb-3"
            (click)="createBook()">
        Add New Book
    </button>

    <table class="table">
        <thead>
            <tr>
                <th scope="col">ID</th>
                <th scope="col">Title</th>
                <th scope="col">Actions</th>
            </tr>
        </thead>

        <tbody>
            <tr *ngFor="let book of books">
                <td>
                    {{book.id}}
                </td>
                <td>
                    {{book.title}}
                </td>
                <td>
                    <!-- 
                      Edit the selected book 
                      when the button is clicked 
                    -->
                    <button type="button"
                            class="btn btn-secondary"
                            (click)="editBook(book)">
                        Edit
                    </button>
                </td>
            </tr>
        </tbody>
    </table>
</div>
```

This is what the UI looks like with the Edit button in each row of the table:

![UI of the book list with Edit button in each row](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4em3f39jyt5low3tdqnb.png)

The `editBook` function has almost the same code as the `createBook` function with just one important difference. It passes the book that we want to edit to the modal dialog by using the model property of the of the dialog options.

```
public editBook(book: Book): void {
    this._modalService.show<Book>(CreateEditBookComponent, {
        title: 'Edit Book',
        // Pass the book object to the modal dialog.
        model: book
    }).result()
        .subscribe(editedBook => {
            let index = this.books?.indexOf(book);
            this.books[index] = editedBook;
        })
}
```

The parameter that we pass to the modal dialog using the `model` property becomes available in the `ModalReference` service in the component used inside the dialog via its `config.model` property.

```
@Component({
    templateUrl: './create-edit-book.component.html'
})
export class CreateEditBookComponent {
    public book: Book = {};

    constructor(
        private readonly _modalReference: ModalReference<Book>
    ) {
        if (this._modalReference.config.model) {
            let copy = { ...this._modalReference.config.model };
            this.book = copy;
        }
    }
}
```

In the code above, we are copying the passed parameter and assigning it to the `book` property of the `CreateEditBookComponent` component. The `model` property of `ModalReference.config` object is passed by reference, so any changes we make in the properties of that object will be reflected in the table where we show the list of books. The reason why we copy it is to not modify the row in the table until the user clicks the "Save" button. If the user clicks the "Cancel" button, the table data will not be updated.

This is that the Edit Book modal dialog looks like:

![UI of the Edit Book modal dialog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ru2pd2zqlmxo7rlrs8ki.png)

## Conclusion
Building a modal dialog for Angular from scratch is not easy, but it is much easier with our modal dialog library. We went through how to setup the library and use it for showing modal dialogs in your project. We went through most basic use cases and features of the dialogs, but the `@developer-partners/ngx-modal-dialog` library has a lot of other features that you may find useful in your real projects. Please follow the link below to learn more about it:
[@developer-partners/ngx-modal-dialog](https://www.npmjs.com/package/@developer-partners/ngx-modal-dialog)

In case if you would like to show a nice loading spinner in your modal dialogs, please see our article about building a loading spinner in Angular:
[How to Create a Loading Spinner in Angular](https://developerpartners.com/blog/f/how-to-create-a-loading-spinner-in-angular)