3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 4

Creating a Declarative Modal

STANDARD

Deciding What to Build First

When I am building an application I try to first focus on getting straight to the thing that my application, or the feature I am working on, “does”. We are going to want to save the data in our application at some point, but that is not what the application is all about. We could start there, but it’s not the best starting point.

The application is about creating and managing checklists, so as a starting point, it makes sense to focus on being able to create a checklist. But how do we get there? It can be intimidating to take in everything all at once, so just focus on whatever the next step is.

If we want to be able to create a checklist, then there is going to need to be some kind of user interface for the user to do that. What will that look like in our application? Well, in this case, we are going to have some kind of add button the user will click when they want to add a new checklist. Let’s focus on that first.

Create the add checklist button

Add the following to the HomeComponent template:

    <header>
      <h1>Quicklists</h1>
      <button>Add Checklist</button>
    </header>

Now we have a button, which currently does nothing. You can go ahead and check for yourself if you like by running:

ng serve

It is a good idea to have this running in the background whilst you are developing your application. The earlier you catch an error the easier it will be to solve. You don’t want to add hundreds of lines of code before you check that your application actually works.

Doing this as a first step might seem a little silly — but it is highly effective especially if you do feel a bit overwhelmed in trying to figure out what to do next.

Create the Checklist interface

Before we continue, it will be useful for us to define the basic structure of what our checklists will look like:

Create a file at src/app/shared/interfaces/checklist.ts and add the following:

export interface Checklist {
  id: string;
  title: string;
}

Our checklist is quite simple, all we need is a title and an id. The purpose of the id is to uniquely identify a particular checklist which will allow us to do things like select one specific checklist from a service or associate checklist items with a particular checklist.

Notice that we are breaking our typical convention of folder types here by creating an interfaces folder. As I mentioned before, it is more common to break the typical data-access/ui/utils convention within the shared folder. We could place our interfaces inside of data-access but personally I like having a dedicated folder for the models/interfaces in the application.

Create a modal

Now we need to move on to what we actually want to do when our add button is clicked. In this case, we are going to launch a modal. A modal is a view that pops on top of your current view. Rather than navigating to a different page to add a checklist, we will have the required form appear in a pop up.

This is a sensible feature to implement first, but it also happens to be one of the more complex aspects of this application, and it also means we will be jumping right into using the Angular CDK.

We’ve got to do it at some point anyway though, and we already have a bunch of experience, so let’s get it done.

We are going to start by creating a new shared dumb component that will allow us to show our modal more easily.

Strictly, this isn’t required — we could just use the Angular CDK wherever we need to create a modal. But this will help make our code more reusable, and it will allow us to use the modal/dialog functionality declaratively (because the Dialog that we will be using from the Angular CDK does not use a declarative API). When we want to use something that is not declarative in our applications, we can generally create a wrapper for it that is declarative.

Create a new file at src/app/shared/ui/modal.component.ts and add the following:

import { Dialog } from '@angular/cdk/dialog';
import {
  Component,
  contentChild,
  input,
  TemplateRef,
  inject,
  effect
} from '@angular/core';

@Component({
  standalone: true,
  selector: 'app-modal',
  template: `<div></div>`,
})
export class ModalComponent {
  dialog = inject(Dialog);
  isOpen = input.required<boolean>();
  template = contentChild.required(TemplateRef);

  constructor() {
    effect(() => {
      const isOpen = this.isOpen();

      if (isOpen) {
        this.dialog.open(this.template(), { panelClass: 'dialog-container' });
      } else {
        this.dialog.closeAll();
      }
    });
  }
}

If this code is freaking you out, don’t worry. Again, we have jumped up in complexity quite a bit and things will actually get a little easier after this.

The general idea is that to use a Dialog from the Angular CDK we can inject it and then we call its open method:

      this.dialog.open(this.template(), { panelClass: 'dialog-container' });

Remember how I said that dumb components should generally not inject dependencies? Well, we’re breaking the rule already here! To be fair, that is why I said generally. An important thing to consider here is that this Dialog is sort of a “helper” or “utility” type of thing — by using Dialog this component isn’t really gaining any knowledge of the application more broadly. It is more of a problem when we inject things like state services into our dumb components, which allows the dumb component to reach into areas of the application it shouldn’t know about and that it shouldn’t depend on.

Do you remember when we talked about an ng-template? It was way back in the basics section so I don’t blame you if you have forgotten. But the basic idea was that we could create sections like this in our templates:

STANDARD
Key

Thanks for checking out the preview of this lesson!

You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).