Angular 10 - Combined Add/Edit (Create/Update) Form Example
Tutorial built with Angular 10.0.14
Other versions available:
- Angular: Angular 14
- React: React Hook Form, Formik
- Next.js: Next.js
This is a quick example of how to build a form in Angular that supports both create and update modes. The form in the example is for creating and updating user data, but the same pattern could be used to build an add/edit form for any type of data.
The below components are part of an Angular CRUD example app I posted yesterday that includes a live demo, so to see the below code running check out Angular - Master Details CRUD Example.
Angular Add/Edit Component Template
The add/edit component template contains a dynamic form that supports both creating and updating users. The isAddMode
property is used to change what is displayed based on which mode it is in ("add mode" vs "edit mode"), for example the form title and password optional message.
<h1 *ngIf="isAddMode">Add User</h1>
<h1 *ngIf="!isAddMode">Edit User</h1>
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="form-row">
<div class="form-group col">
<label>Title</label>
<select formControlName="title" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.title.errors }">
<option value=""></option>
<option value="Mr">Mr</option>
<option value="Mrs">Mrs</option>
<option value="Miss">Miss</option>
<option value="Ms">Ms</option>
</select>
<div *ngIf="submitted && f.title.errors" class="invalid-feedback">
<div *ngIf="f.title.errors.required">Title is required</div>
</div>
</div>
<div class="form-group col-5">
<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="form-group col-5">
<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="form-row">
<div class="form-group col-7">
<label>Email</label>
<input type="text" formControlName="email" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.email.errors }" />
<div *ngIf="submitted && f.email.errors" class="invalid-feedback">
<div *ngIf="f.email.errors.required">Email is required</div>
<div *ngIf="f.email.errors.email">Email must be a valid email address</div>
</div>
</div>
<div class="form-group col">
<label>Role</label>
<select formControlName="role" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.role.errors }">
<option value=""></option>
<option value="User">User</option>
<option value="Admin">Admin</option>
</select>
<div *ngIf="submitted && f.role.errors" class="invalid-feedback">
<div *ngIf="f.role.errors.required">Role is required</div>
</div>
</div>
</div>
<div *ngIf="!isAddMode">
<h3 class="pt-3">Change Password</h3>
<p>Leave blank to keep the same password</p>
</div>
<div class="form-row">
<div class="form-group col">
<label>Password</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 class="form-group col">
<label>Confirm Password</label>
<input type="password" formControlName="confirmPassword" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.confirmPassword.errors }" />
<div *ngIf="submitted && f.confirmPassword.errors" class="invalid-feedback">
<div *ngIf="f.confirmPassword.errors.required">Confirm Password is required</div>
<div *ngIf="f.confirmPassword.errors.mustMatch">Passwords must match</div>
</div>
</div>
</div>
<div class="form-group">
<button [disabled]="loading" class="btn btn-primary">
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
Save
</button>
<a routerLink="/users" class="btn btn-link">Cancel</a>
</div>
</form>
Angular Add/Edit Component
The add/edit component is used for both adding and editing users in the angular app, the component is in "add mode" when there is no user id route parameter, otherwise it is in "edit mode". The property isAddMode
is used to change the component behaviour based on which mode it is in, for example in "add mode" the password field is required, and in "edit mode" (!this.isAddMode
) the user service is called when the component initializes to get the user details (this.userService.getById(this.id)
) to pre-populate the form.
On submit a user is either created or updated by calling the user 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 { UserService, AlertService } from '@app/_services';
import { MustMatch } from '@app/_helpers';
@Component({ templateUrl: 'add-edit.component.html' })
export class AddEditComponent implements OnInit {
form: FormGroup;
id: string;
isAddMode: boolean;
loading = false;
submitted = false;
constructor(
private formBuilder: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private userService: UserService,
private alertService: AlertService
) {}
ngOnInit() {
this.id = this.route.snapshot.params['id'];
this.isAddMode = !this.id;
// password not required in edit mode
const passwordValidators = [Validators.minLength(6)];
if (this.isAddMode) {
passwordValidators.push(Validators.required);
}
this.form = this.formBuilder.group({
title: ['', Validators.required],
firstName: ['', Validators.required],
lastName: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
role: ['', Validators.required],
password: ['', [Validators.minLength(6), this.isAddMode ? Validators.required : Validators.nullValidator]],
confirmPassword: ['', this.isAddMode ? Validators.required : Validators.nullValidator]
}, {
validator: MustMatch('password', 'confirmPassword')
});
if (!this.isAddMode) {
this.userService.getById(this.id)
.pipe(first())
.subscribe(x => this.form.patchValue(x));
}
}
// 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.loading = true;
if (this.isAddMode) {
this.createUser();
} else {
this.updateUser();
}
}
private createUser() {
this.userService.create(this.form.value)
.pipe(first())
.subscribe({
next: () => {
this.alertService.success('User added', { keepAfterRouteChange: true });
this.router.navigate(['../'], { relativeTo: this.route });
},
error: error => {
this.alertService.error(error);
this.loading = false;
}
});
}
private updateUser() {
this.userService.update(this.id, this.form.value)
.pipe(first())
.subscribe({
next: () => {
this.alertService.success('User updated', { keepAfterRouteChange: true });
this.router.navigate(['../../'], { relativeTo: this.route });
},
error: error => {
this.alertService.error(error);
this.loading = false;
}
});
}
}
Users Routing Module
The users routing module defines the routes for each of the pages of the users section.
The second and third routes are for adding and editing users, they match different routes but both load the above component (AddEditComponent
) which 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 Help?
Search fiverr for freelance Angular 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!