DEV Community

Cover image for Easy Modals with Angular
Siddhant Jaiswal for This is Angular

Posted on • Updated on

Easy Modals with Angular

Hello Devs,

We all might have (users of #angular) to create modals at some point or the other. BUT creating a reusable modal is not easy in Angular. Now there are third party libraries which will help you get the work done but that itself has its learning curve and sometimes it's difficult to collaborate with them.

The Angular team offers a solution, via the Angular CDK module.

The Idea!

The idea is to create a simple modal with Overlay Modal using Ng-Template, also making sure it's WCAG 2.0 accessible.

The Overlay Module

Provided by Angular CDK it's one of the simplest ways to create Modals in angular. With easy access to modal data and functions.
The overlay package provides an effortless way to create floating panels on screen. It can be used to create a floating dropdown menu, modals, etc. and since the CDK modules are divided into small components we only need to import the packages we need.

Lets Begin.

First, we need to install Angular CDK module to our existing project and to do that just run the following command in the root of your project.

npm install @angular/cdk
or
ng add @angular/cdk
Enter fullscreen mode Exit fullscreen mode

Next up we need to import just one module in the app.module.ts file and add that in the imports array as well.

import { OverlayModule } from '@angular/cdk/overlay';
....

imports: [
...
OverlayModule
]
Enter fullscreen mode Exit fullscreen mode

Basics of OverlayModule

Now to create an overlay we need to call create() method. This method returns an OverlayRef which is a PortalOutlet.
Now this Portal package gives us a flexible way of rendering dynamic content. (More on this later).
This create() method takes a config object which helps us define certain parameters of the content will look on the screen. The most used parameters are

backdropClass: string [] //Adds the custom class for backdrop of modal.
hasBackdrop: boolean //Whether overlay will have backdrop or not. 
height: number | string //defines the height of overlay. 
width: number | string //defines the width of overlay.
pannelClass: string [] //Custom class for overlay. 
positionStrategy: PositionStrategy //How will the overlay be placed on screen. 
scrollStrategy: ScrollStrategy //how scrolling will happen on the overlay. 
Enter fullscreen mode Exit fullscreen mode

These are some of the parameters that we pass in create() method.

const overlayRef = overlay.create({
  height: '20vh',
  width: '80vw',
  .... // an example of creating an overlay
});
Enter fullscreen mode Exit fullscreen mode

This overlayRef has attach() method that takes in the PortalOutlet instance as parameter to display the overlay. This PortalOutlet instance will hold our UI elements.

Portals

A portal is a UI that can be rendered dynamically. Now this portal can be a Component or TemplateRef or a DOM element.
This provides a dynamic way of rendering UI elements on the page.
We will be working with TemplatePortal, to create a template protal first we need to create a ng-template block which is basically our modal strucutre.
TemplatePortal allows you to take Angular content within one template and render it somewhere else.

<ng-template #template >
    <div class="modal-card" >
        <header class="modal-card-head">
            <h5 tabindex="0" >{{title}}</h5>
            <a tabindex="0" (click)="closeModal()" (keydown)="handleKey($event)"  role="button" aria-label="Close Modal">
                <span>X</span>
            </a>
        </header>
        <section class="modal-card-body">
           <p class="regular" tabindex="0">{{body}}</p>
        </section>
    </div>
</ng-template>
Enter fullscreen mode Exit fullscreen mode

It's a considerably basic modal structure, modal-card is the wrapper inside which we have two sections one is for the title and other is for the body/content.

Now that our structure is ready, we need to access this template inside our component.ts file, and to do that we need import TemplateRef ViewChild and ViewContainerRef.

@ViewChild('template') tpl! : TemplateRef<unknown>;

constructor(..., private viewContainerRef: ViewContainerRef);
Enter fullscreen mode Exit fullscreen mode

Now once all our imports are taken care we need to attach the template to our OverlayRef.

openWithTemplate() {
    const config = new OverlayConfig({
      hasBackdrop: true,
      panelClass: ['modal', 'active'],
      backdropClass: 'modal-backdrop',
      scrollStrategy: this.overlay.scrollStrategies.block(),
    });
    this.overlayRef = this.overlay.create(config);
    this.overlayRef.attach(new TemplatePortal(this.tpl, this.viewContainerRef)); 
  }
Enter fullscreen mode Exit fullscreen mode

So, this is our final openWithTemplate() function which can be used to open the Modal just map with any button's click funtion and the modal will open.
As for the closing the modal its quite simple as well. Look at the html structure of modal and you can see we have mapped a close() function to anchor tag. The function is code is:

closeModal(){
    this.overlayRef.dispose();
  }

Enter fullscreen mode Exit fullscreen mode

and this is reason we opted to create overlayRef as global variable.

Accessibility

In today's world accessibility is especially important as we must make sure to be as inclusive as possible and give extra thought while building web applications. There is a substantial number of users across the globe who depend on screen readers and other assistive technology. The World Wide Web Consortium has laid out a set of guidelines that must be followed when building any web application known as Web Content Accessibility Guidelines or WCAG in short. The Angular team is working hard on this and thus make sures that we the developers have all the right tools to make our applications WCAG complaint. Angular CDK provides A11yModule to help us in achieving WCAG standards.

As per the WCAG standard the focus should be inside the Modal window once it is open and should remain inside until the modal is closed. We also must make sure that all the elements inside the modal are accessible to the end user, meaning when user focuses on any element inside the modal that screen reader should announce it. Some of the basic accessibility properties were already set in the initial HTML code of the modal that was presented before like the anchor tag has role=button this property does nothing visually but is important for assistive techs in understanding that this anchor is not a link but is acting as button. Similarly, we have aria-label to indicate the button title which is Close thus the screen reader will announce it as Close Button.
Apart from this we have also set some tabindex on multiple elements to make sure they get focus as the user uses TAB Key to navigate, but once the user reaches the last tabindex in the modal and hits tab again he will move out of the modal even though the modal is still open and we want our users to stay inside it. And this were A11yModeule will help us.

First thing is that we must make sure the focus is transferred to modal as soon as it opens for this, we will be using cdkFocusInitial directive what this thus is sets an initial focus point for the modal. Next, We have to trap the focus inside the modal and for this we have cdkTrapFocus & cdkTrapFocusAutoCapture

cdkTrapFocusAutoCapture: Whether the directive should automatically move focus into the trapped region upon initialization and return focus to the previous activeElement upon destruction.

cdkTrapFocus: Whether the focus trap is active.

Applying these accessibility helper directives to our code and it looks something like this.

<ng-template #template >
    <div class="modal-card" cdkTrapFocus cdkTrapFocusAutoCapture>
        <header class="modal-card-head">
            <h5 tabindex="0" cdkFocusInitial>{{title}}</h5>
            <a tabindex="0" (click)="closeModal()" (keydown)="handleKey($event)"  role="button" aria-label="Close Modal">
                <span>X</span>
            </a>
        </header>
        <section class="modal-card-body">
            <p class="regular" tabindex="0">{{body}}</p>
        </section>
    </div>
</ng-template>
Enter fullscreen mode Exit fullscreen mode


`

We are giving cdkTrapFocus & cdkTrapFocusAutoCapture to modal container element thus making sure the focus is rotated inside this container until and unless the modal is closed. And ckdFocusInitial is added to h5 element as it is the title of the modal. Just by adding these three directives and certain aria properties we have converted our modal to WCAG 2.0 complaint.

Here is a sample modal I have been working on:

Sample Use Case

Conclusion

To make this modal reusable we can convert the ng-template code into its own separate component and now we have modal that works throughout the application without writing any custom service for the same. This is a very simple way of creating modals with accessibility.

I would love to hear your thoughts on this and how can we improve this code. Lets collaborate in a fun way.

Check out my portfolio Find Sid.

Lets connect on LinkedIn
Checkout my artwork on Instagram, a follow will be nice.

Discussion (0)