3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 5

Creating a Form Modal Component

Our first dumb component

STANDARD

Creating a Form Modal Component

In this lesson, we are going to create our first dumb/presentational component and it is going to be one that is shared with multiple features. For the home feature that we are currently working on we need to ability to display a form inside of the modal we are launching to allow the user to create a new checklist.

We could just create a dumb component specifically for the home feature, but we are also going to need to do the exact same thing when we get to adding items to individual checklists - we will again need to display a form inside of a modal. We might decide to just manually hardcode forms for each of these features rather than having a single shared form component, but since our forms are going to be so simple (we basically just need to accept a single text input) it will be relatively easy to create a single component that can be shared with both features.

Create a Checklist Interface

First, we are going to define precisely what a Checklist is in our application by creating an interface.

Create a file at 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 checklists 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 the Form Modal Component

Now we will create our dumb/presentational form component, also without our shared folder.

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

import { CommonModule } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  EventEmitter,
  Input,
  NgModule,
  Output,
} from '@angular/core';
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';

@Component({
  selector: 'app-form-modal',
  template: `
    <ion-header>
      <ion-toolbar>
        <ion-title>{{ title }}</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content>
      <form [formGroup]="formGroup" (ngSubmit)="handleSave()">
        <ion-item *ngFor="let control of formGroup.controls | keyvalue">
          <ion-label position="stacked">{{ control.key }}</ion-label>
          <ion-input type="text" [formControlName]="control.key"></ion-input>
        </ion-item>
        <ion-button
          color="dark"
          expand="full"
          type="submit"
          [disabled]="!formGroup.valid"
        >
          <ion-icon slot="start" name="save-outline"></ion-icon> Save
        </ion-button>
      </form>
    </ion-content>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FormModalComponent {
  @Input() title!: string;
  @Input() formGroup!: FormGroup;

  @Output() save = new EventEmitter<boolean>();

  handleSave() {
    this.save.emit(true);
  }
}

@NgModule({
  imports: [CommonModule, ReactiveFormsModule, IonicModule],
  declarations: [FormModalComponent],
  exports: [FormModalComponent],
})
export class FormModalComponentModule {}

There is a bit going on here, so let’s talk through what is going on. Let’s start with the class:

export class FormModalComponent {
  @Input() title!: string;
  @Input() formGroup!: FormGroup;

  @Output() save = new EventEmitter<boolean>();

  handleSave() {
    this.save.emit(true);
  }
}

Remember that this is a dumb component, so generally it is not going to inject any dependencies and it doesn’t know about anything that is happening in the broader application. It just gets its inputs, and sends outputs to communicate with whatever parent component is using it (the dumb child component doesn’t even know what component is using it).

In this case, we have two inputs. We want to be able to configure the title to be displayed in the template, and we also allow the parent component to supply a FormGroup as an input. This is what will allow the parent component to configure what form fields to display. We will render out an input in the template for each control defined in the FormGroup.

We also have a single save output that is used to indicate to the parent component when the save button has been clicked. Let’s take a closer look at the template now:

    <ion-header>
      <ion-toolbar>
        <ion-title>{{ title }}</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content>
      <form [formGroup]="formGroup" (ngSubmit)="handleSave()">
        <ion-item *ngFor="let control of formGroup.controls | keyvalue">
          <ion-label position="stacked">{{ control.key }}</ion-label>
          <ion-input type="text" [formControlName]="control.key"></ion-input>
        </ion-item>
        <ion-button
          color="dark"
          expand="full"
          type="submit"
          [disabled]="!formGroup.valid"
        >
          <ion-icon slot="start" name="save-outline"></ion-icon> Save
        </ion-button>
      </form>
    </ion-content>

Most of this is just the standard structure of an Ionic page, but there are a few interesting things going on. Angular hooks into the functionality of the standard HTML <form> element, which we can activate by binding our FormGroup to it using the formGroup directive:

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).