How to Combine Add and Edit Forms in Angular
Create/update form with smart/dumb components

Search for a command to run...
Create/update form with smart/dumb components

No comments yet. Be the first to comment.
When applications make HTTP requests and fail, we need to handle them. Ideally, we take care of these errors in one place in our code. In this article, we discover how to handle these exceptions in Angular by using an interceptor. We look into the be...

In this article, we create a complete Breakout-style game. The HTML Canvas provides the game container where we draw graphics via JavaScript. After learning how to use the Canvas for graphics and animations, we go through the algorithms behind collis...

Or how my dreams of writing a game started by animating a square block

You Will Be Surprised By Number 9!

This article demonstrates how to build a reusable form component that supports add and edit modes. We do this by using the container and presentation components architecture. The goal is not about teaching forms, so we keep it as simple as possible using reactive forms.
We create a simplified form without any validation to add and edit medals. The medal has three properties:
export interface Medal {
name: string;
type: string;
sport: string;
}
With reactive forms, the [formGroup] is a directive that we bind to and pass the form object in:
<h1 *ngIf="!medal">Add Medal</h1>
<h1 *ngIf="medal">Edit Medal</h1>
<form [formGroup]="form" (ngSubmit)="submit()">
<label>Name</label>
<input type="text" formControlName="name" /><br />
<label>Type</label>
<input type="text" formControlName="type" /><br />
<label>Sport</label>
<input type="text" formControlName="sport" /><br />
<button type="submit">Submit</button>
</form>
We inject the FormBuilder service and use the group() method to create the form controls matching the template:
import {
ChangeDetectionStrategy, Component, EventEmitter,
Input, OnInit, OnChanges, Output, SimpleChanges
} from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { Medal } from '../app.component';
@Component({
selector: 'medal-form',
templateUrl: 'medal-form.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MedalFormComponent implements OnInit, OnChanges {
@Input() medal: Medal;
@Output() submitted = new EventEmitter<Medal>();
form: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.form = this.fb.group({
name: [''],
type: [null],
sport: [null],
});
}
ngOnChanges(changes: SimpleChanges) {
if (changes.medal?.currentValue) {
this.form?.patchValue(this.medal);
}
}
submit() {
this.submitted.emit(this.form.getRawValue());
this.form.reset();
}
}
We use the Input() decorator for the medal property. Then we use it when we have data that we can send to the child component. To watch for changes on an Input() property, we use the OnChanges lifecycle hook. Whenever it detects changes to the medal property, we populate the form with patchValue(). When submit is pressed, we emit the form values through the Output() property submitted.
We have implemented our reusable form component as a dumb component. Now let us talk more about the architecture we chose and how we use the form component we created.
Let us first consider why we want to split into these two components. When using only one, we need to subscribe() to the observable data that Angular is keen for us to use. There are disadvantages to manually subscribing to observables. Some can lead to bugs that can be hard to debug. Using subscribe() also requires us to unsubscribe at the end of the component lifecycle to avoid memory leaks.
Subscribing to the observable manually in the ngOnInit() doesn't always work with the preferred OnPush change detection strategy out of the box. We sometimes need to tell Angular change detection manually when we want it to run. Needless to say, when someone comes to me for help with code where the data is not updating for some reason, the first thing I do is look for a subscribe() in the code.
The next, better solution is to use the async pipe. But, this also has some downsides. Objects have to be unwrapped, sometimes multiple times, in the template using *ngIf="data$ | async as data".
Properties unwrapped using *ngIf or *ngFor are not accessible in the component's methods. We have to pass these properties to the methods from the template as method parameters which makes the code harder to read. And let me not get started about the testing.
So how can we then solve this better?
For a better architecture, we split components into two types of specialized components:
The responsibility of the dumb component is to present the data, while the smart one is responsible for fetching and managing the data. Presentation components should be child components of the container components on your page.
Interaction between smart and dumb components is done by:
Input -presentation component receives data from parentOutput -presentation component triggers actions that parent listens toBy doing this, the presentation component remains isolated from the parent container component via a clearly defined interface.
For the final piece of the puzzle, here are the ways we can use our presentational form as a create form:
<medal-form
(submitted)="onSubmitted($event)"
></medal-form>
We don't send any data in, so we get an empty form from which we get notified when it is submitted. Then the only thing left for us to do is call the backend through our store or service.
In the edit form we fetch the data and send it into the form through an async pipe:
<medal-form
[medal]="medal$ | async"
(submitted)="onSubmitted($event)"
></medal-form>
Now, we let the framework handle the subscriptions for us. The presentational component manages the data as objects instead of observables.
I have created a playground for you to play with the code. There is no data fetching or async pipe used, but it gives you an idea of how it works.
In this article, we combined two forms by making a presentational component. When we send it data with an async pipe, it receives the unwrapped object and populates the form with it. This architecture gives us a cleaner, more robust solution that hopefully keeps the bugs away.