How to Build a Form in Angular - Combined Add/Edit (Create/Update) Form Example

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.

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">
             
            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 { }

Subscribe or Follow Me For Updates

Subscribe to YouTube channel or follow on GitHub to be notified when I post new content.

#angular #javascript

How to Build a Form in Angular - Combined Add/Edit (Create/Update) Form Example
21.60 GEEK