Angular 14 - Dynamic Add/Edit Form that Supports Create and Update Mode
Tutorial built with Angular 14.2.12
Other versions available:
- Angular: Angular 10
- React: React Hook Form, Formik
- Next.js: Next.js
This is a quick post on how to build a form that supports both adding and editing data in Angular 14 with Reactive Forms.
Forms for creating and updating records can often contain a lot of the same code. If the code is similar enough it can make more sense to combine the functionality into a single dynamic form than to maintain two files that are almost identical.
The example code is from a Angular Auth + CRUD tutorial I posted recently which allows adding and editing user data after login. For the full tutorial including a live demo see Angular 14 - User Registration and Login Example & Tutorial.
Users Add/Edit Component Template
The users add/edit component template contains a dynamic form that supports both adding and editing users. The form is in edit mode when there a user id
property in the current route, otherwise it is in add mode.
In edit mode the form is pre-populated with user details fetched from the API and the password field is optional. The dynamic behaviour is implemented in the users add/edit component.
<h1>{{title}}</h1>
<form *ngIf="!loading" [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="row">
<div class="mb-3 col">
<label class="form-label">First Name</label>
<input type="text" formControlName="firstName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.firstName.errors }" />
<div *ngIf="submitted && f.firstName.errors" class="invalid-feedback">
<div *ngIf="f.firstName.errors.required">First Name is required</div>
</div>
</div>
<div class="mb-3 col">
<label class="form-label">Last Name</label>
<input type="text" formControlName="lastName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.lastName.errors }" />
<div *ngIf="submitted && f.lastName.errors" class="invalid-feedback">
<div *ngIf="f.lastName.errors.required">Last Name is required</div>
</div>
</div>
</div>
<div class="row">
<div class="mb-3 col">
<label class="form-label">Username</label>
<input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.username.errors }" />
<div *ngIf="submitted && f.username.errors" class="invalid-feedback">
<div *ngIf="f.username.errors.required">Username is required</div>
</div>
</div>
<div class="mb-3 col">
<label class="form-label">
Password
<em *ngIf="id">(Leave blank to keep the same password)</em>
</label>
<input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
<div *ngIf="submitted && f.password.errors" class="invalid-feedback">
<div *ngIf="f.password.errors.required">Password is required</div>
<div *ngIf="f.password.errors.minlength">Password must be at least 6 characters</div>
</div>
</div>
</div>
<div class="mb-3">
<button [disabled]="submitting" class="btn btn-primary">
<span *ngIf="submitting" class="spinner-border spinner-border-sm me-1"></span>
Save
</button>
<a routerLink="/users" class="btn btn-link">Cancel</a>
</div>
</form>
<div *ngIf="loading" class="text-center m-5">
<span class="spinner-border spinner-border-lg align-center"></span>
</div>
Users Add/Edit Component
The users add/edit component is used for both adding and editing users in the angular tutorial app, the component is in edit mode when there a user id
route parameter, otherwise it is in add mode.
In add mode the password field is required and the form fields are empty by default. In edit mode the password field is optional and the form is pre-populated with the specified user details, which are fetched from the API with the account service. For more info on validation with Reactive Forms see Angular 14 - Reactive Forms Validation Example.
On submit a user is either created or updated by calling the account service, and on success you are redirected back to the users list page with a success message.
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first } from 'rxjs/operators';
import { AccountService, AlertService } from '@app/_services';
@Component({ templateUrl: 'add-edit.component.html' })
export class AddEditComponent implements OnInit {
form!: FormGroup;
id?: string;
title!: string;
loading = false;
submitting = false;
submitted = false;
constructor(
private formBuilder: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private accountService: AccountService,
private alertService: AlertService
) { }
ngOnInit() {
this.id = this.route.snapshot.params['id'];
// form with validation rules
this.form = this.formBuilder.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
username: ['', Validators.required],
// password only required in add mode
password: ['', [Validators.minLength(6), ...(!this.id ? [Validators.required] : [])]]
});
this.title = 'Add User';
if (this.id) {
// edit mode
this.title = 'Edit User';
this.loading = true;
this.accountService.getById(this.id)
.pipe(first())
.subscribe(x => {
this.form.patchValue(x);
this.loading = false;
});
}
}
// convenience getter for easy access to form fields
get f() { return this.form.controls; }
onSubmit() {
this.submitted = true;
// reset alerts on submit
this.alertService.clear();
// stop here if form is invalid
if (this.form.invalid) {
return;
}
this.submitting = true;
this.saveUser()
.pipe(first())
.subscribe({
next: () => {
this.alertService.success('User saved', { keepAfterRouteChange: true });
this.router.navigateByUrl('/users');
},
error: error => {
this.alertService.error(error);
this.submitting = false;
}
})
}
private saveUser() {
// create or update user based on id param
return this.id
? this.accountService.update(this.id!, this.form.value)
: this.accountService.register(this.form.value);
}
}
Angular Users Routing Module
The users routing module defines the routes for the /users
section of the Angular app. It includes routes for listing, adding and editing users, and a parent route for the LayoutComponent
which contains the common layout code for all pages in the users section.
The add and edit routes have different paths ('add'
& 'edit/:id'
) but both load the same component (AddEditComponent
) which dynamically modifies its behaviour based on the route.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LayoutComponent } from './layout.component';
import { ListComponent } from './list.component';
import { AddEditComponent } from './add-edit.component';
const routes: Routes = [
{
path: '', component: LayoutComponent,
children: [
{ path: '', component: ListComponent },
{ path: 'add', component: AddEditComponent },
{ path: 'edit/:id', component: AddEditComponent }
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class UsersRoutingModule { }
Need Some Angular 14 Help?
Search fiverr for freelance Angular 14 developers.
Follow me for updates
When I'm not coding...
Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!