In modern web application User experience is top most priority, many UX design would be more challenging to achieve without compromising coding standards and validation.

I like to share one of the design I came across and the solution I applied.

Here is multi tab design, here user information like personal detail, contact detail & payment details are folded into the tab. Instead of showing all the information in one single long page.

Here you can update any of those details and hit save button at any point of time.

Design like this is very good for End Users and really challenging for developers. Because save button is common among the tabs and save action is only for the active tab.

Solution # 1: HTML5 Form attribute in button tag

We can add form attribute the button tag and html will take care of submitting appropriate form, in angular we can dynamically change form attribute value based on active tab / form

Browser support for this solution is not 100%, if you are targeting all the major browser ( IE, Safari, Chrome, Firefox, etc ) with all the version. This is not an ideal solution

Solution # 2: Associate label with submit input

<h1>User Settings</h1>
<h2 class="ui sub header">Update user personal , contact & payment details</h2>
<div class="ui secondary pointing menu">
  <a class="item" [routerLink]="['/personal']" routerLinkActive="active" >
    <i class="icon address book"></i>Personal Detail
  </a>
  <a class="item" [routerLink]="['/contact']" routerLinkActive="active" >
    <i class="icon address card"></i>Contact Detail
  </a>
  <a class="item" [routerLink]="['/payment']" routerLinkActive="active" >
    <i class="icon credit card"></i>Payment Detail
  </a>
</div>
<div class="ui segment">
  <router-outlet></router-outlet>
</div>

<label for="save-form" class="ui button primary">Save</label>

I created components for individual tab and loading its content via angular routes. So actively only one tab content will be available in DOM. Each component is wrapped with form tag and it has one input type=”submit” which is hidden and id attribute value is matching for attribute value of label tag which is outside of the form

<label for="save-form" class="ui button primary">Save</label>
<input type="submit" value="Save" hidden id="save-form">

Contact component template

<form class="ui form" [formGroup]="contactForm">
    <div class="field">
        <label for="address-field">Primary Address</label>
        <div class="fields">
            <div class="twelve wide field" [ngClass]="{'error': hasAddressErrors}">
                <input type="text" id="address-field" name="address-line" placeholder="Street Address" formControlName="address">
            </div>
            <div class="four wide field" [ngClass]="{'error': hasAptErrors}">
                <input type="text" name="apt" placeholder="Apt #" formControlName="apt">
            </div>
        </div>
    </div>
    <div class="two fields">
        <div class="field" [ngClass]="{'error': hasStateErrors}">
            <label for="state-field">State</label>
            <select id="state-field" class="ui fluid dropdown" formControlName="state">
                <option value="">State</option>
                <option value="AL">Alabama</option>
            </select>
        </div>
        <div class="field" [ngClass]="{'error': hasZipErrors}">
            <label for="zip">Zip</label>
            <input type="number" id="zip" name="zip" placeholder="00000" formControlName="zip">
        </div>
    </div>
    <input type="submit" value="Save" hidden id="save-form">
</form>

Contact component typescript

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { FormBuilder, FormGroup, Validators, AbstractControl } from '@angular/forms';
import { AppFormGroup } from '../shared/directives/form-group.class';

@Component({
  selector: 'app-contact',
  templateUrl: './contact.component.html',
  changeDetection : ChangeDetectionStrategy.OnPush
})
export class ContactComponent implements OnInit {

  public contactForm: AppFormGroup;

  constructor(private fb: FormBuilder) { }

  ngOnInit() {
    this.contactForm = this.fb.group({
      address : ['', Validators.required],
      apt : [''],
      state : ['', Validators.required],
      zip : ['', Validators.required]
    });
  }

  public get hasAddressErrors(): boolean {
    const formControl = this.contactForm.controls.address;
    return this.isFormFieldValid(formControl);
  }

  public get hasAptErrors(): boolean {
    const formControl = this.contactForm.controls.apt;
    return this.isFormFieldValid(formControl);
  }

  public get hasStateErrors(): boolean {
    const formControl = this.contactForm.controls.state;
    return this.isFormFieldValid(formControl);
  }

  public get hasZipErrors(): boolean {
    const formControl = this.contactForm.controls.zip;
    return this.isFormFieldValid(formControl);
  }

  private isFormFieldValid(formControl: AbstractControl): boolean {
    return (this.contactForm.submitted || formControl.touched) && formControl.invalid;
  }

}

When user click save label which is outside of the form, html will trigger click event for the input field which is matching id. Which will naturally turn into submit event in form.

This solution will work in all browsers and its not just for angular framework, you can apply this approach in any framework like ReactJs, Vue or Imba

For live in action, please visit this page “angular-form-submit” and to view or download full code please visit GitHub link

I explained in my one of the previous post. If you like to know how to show required validation errors when form is submitted without touching any form fields.

Categories: Angular

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.