Skip to content
View lessons Login

Launch sale! Get 30% OFF expires in 126:46:22

3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Creating a Form Modal Component

Creating a Form Modal Component

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

The form-modal is probably the most difficult feature in the application. We are going to touch on some somewhat advanced concepts here. Don’t feel too worried if things aren’t making complete sense.

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 in the checklist feature we will create later — we will again need to display a form inside of a modal. We might decide to just manually hard code 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 the Form Modal Component

Now we will create our dumb/presentational form component.

import { KeyValuePipe } from '@angular/common';
import { Component, inject, input, output } from '@angular/core';
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
import {
IonButton,
IonButtons,
IonHeader,
IonIcon,
IonInput,
IonItem,
IonTitle,
IonToolbar,
ModalController,
} from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { close } from 'ionicons/icons';
@Component({
selector: 'app-form-modal',
template: `
<ion-header>
<ion-toolbar>
<ion-title>{{ title() }}</ion-title>
<ion-buttons slot="end">
<ion-button (click)="modalCtrl.dismiss()">
<ion-icon name="close" slot="icon-only"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<form
[formGroup]="formGroup()"
(ngSubmit)="save.emit(); modalCtrl.dismiss()"
>
@for (control of formGroup().controls | keyvalue; track control.key) {
<ion-item>
<ion-input
[label]="control.key"
labelPlacement="stacked"
[id]="control.key"
type="text"
[formControlName]="control.key"
></ion-input>
</ion-item>
}
<ion-button expand="full" type="submit">Save</ion-button>
</form>
`,
imports: [
ReactiveFormsModule,
KeyValuePipe,
IonHeader,
IonToolbar,
IonTitle,
IonItem,
IonInput,
IonButton,
IonButtons,
IonIcon,
],
})
export class FormModalComponent {
modalCtrl = inject(ModalController);
formGroup = input.required<FormGroup>();
title = input.required<string>();
save = output();
constructor() {
addIcons({ close });
}
}

This is the component in its entirety. It is a somewhat complex component, but also reasonably within the realms of the concepts we have been learning so far.

The only thing we haven’t actually seen yet here is the keyvalue pipe which we also add to the imports array through KeyValuePipe. The idea here is that this component will be given a FormGroup which contains form controls (e.g. we might have a username form control). The keyvalue pipe will allow us to access the key and value in these control objects. The idea is that we want to use the key, which is actually the name of the form control, and assign that as the formControlName for the input. In this way, the specific inputs we are dynamically rendering out will be correctly associated with their corresponding form control — that means updating the input field will update the form control’s value.

That is the most complex part here. We will talk through the rest in just a moment, but this is a good opportunity to just take a look at the code and see if you can understand generally what is happening.

Again, don’t worry if it isn’t all making sense. There is nothing you need to do right now, just see what you can figure out about the code before moving on.

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 {
modalCtrl = inject(ModalController);
formGroup = input.required<FormGroup>();
title = input.required<string>();
save = output();
constructor() {
addIcons({ close });
}
}

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 (using the technique we talked about above).

We also have a save output that is used to indicate to the parent component when the save button has been clicked.

As well as this, we inject the ModalController so that we can dismiss the modal when the form is submitted. Our parent/smart component will be able to listen for this dismiss and react appropriately.

Let’s take a closer look at the template now:

<ion-header>
<ion-toolbar>
<ion-title>{{ title() }}</ion-title>
<ion-buttons slot="end">
<ion-button (click)="modalCtrl.dismiss()">
<ion-icon name="close" slot="icon-only"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<form
[formGroup]="formGroup()"
(ngSubmit)="save.emit(); modalCtrl.dismiss()"
>
@for (control of formGroup().controls | keyvalue; track control.key) {
<ion-item>
<ion-input
[label]="control.key"
labelPlacement="stacked"
[id]="control.key"
type="text"
[formControlName]="control.key"
></ion-input>
</ion-item>
}
<ion-button expand="full" type="submit">Save</ion-button>
</form>
STANDARD STANDARD

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