14. Forms: Reactive Forms

DataModel:

export class Technology {
  id = 0;
  name = '';
  descriptions: Descriptions[];
}

export class Descriptions {
  developer = '';
  initialRelease = '';
  stableRelease = '';
  details = '';
}

export const technologies: Technology[] = [
    {
      id: 1,
      name: 'Angular',
      descriptions: [
      { developer: 'Google', initialRelease: 'October 20 2010', stableRelease: '1.6.6 / August 18 2017', details: 'SuperHeroic JavaScript MV* Open Source Framework.' },
      { developer: 'Google', initialRelease: 'September 14 2016', stableRelease: '4.4.4 / 28 September 2017', details: 'One Framework. Mobile & desktop. Open Source.'}
      ]
    },
    {
      id: 2,
      name: 'Angular CLI',
      descriptions: [
        { developer: 'Google', initialRelease: 'December 4 2015', stableRelease: '1.4.9 / October 23 2017', details: 'Command Line Interface for Angular, tool to initialize, scaffold, and maintain Angular applications.'}
      ]
    },
    {
      id: 3,
      name: 'Angular Material',
      descriptions: []
    }
];

export const developer = [ 'Google', 'Humanity', 'Open Source', 'GitHub'];

TechnologyReactiveDetailComponent1:

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-technology-reactive-detail',
  template: `
        <h2>Technology Reactive Detail</h2>
        <h3><i>Just a FormControl</i></h3>
        <label class="center-block">Name:
        <input class="form-control" [formControl]="name">
        </label>
  `
})
export class TechnologyReactiveDetailComponent1 {
  name = new FormControl();

}

AppComponent:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
        <h1>{{ title }}</h1>
          <div class="left">
            <h1>Reactive Forms</h1>
            <app-technology-reactive-detail></app-technology-reactive-detail>
          </div>
          <div class="right">
            <img [src]="imageUrl" class="img-round" alt="princess image">
          </div>
  `,
  styles: [`h1 { font-weight: bold; }`]
})
export class AppComponent {
  title = 'Tour of Technologies';
  imageUrl = '../assets/polymer1.jpg-large';

}

Let's view the running app in Chromium:

FormGroup:

import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-technology-reactive-detail2',
  template: `
        <h2>Technology Reactive Detail</h2>
        <h3><i>FormControl in a FormGroup</i></h3>
        <form [formGroup]="technologyForm" novalidate>
              <div class="form-group">
                <label class="center-block">Name:
                    <input class="form-control" formControlName="name">
                </label>
              </div>
        </form>
        <p>Form value: {{ technologyForm.value | json }}</p>
        <p>Form status: {{ technologyForm.status | json }}</p>
  `
})
export class TechnologyReactiveDetailComponent2 {
  technologyForm = new FormGroup({
  name: new FormControl()
  });

}

FormBuilder:

import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-technology-reactive-detail3',
  template: `
        <h2>Technology Reactive Detail</h2>
        <h3><i>FormGroup with single FormControl using FormBuilder</i></h3>
        <form [formGroup]="technologyForm" novalidate>
              <div class="form-group">
                <label class="center-block">Name:
                    <input class="form-control" formControlName="name">
                </label>
              </div>
        </form>
        <p>Form value: {{ technologyForm.value | json }}</p>
        <p>Form status: {{ technologyForm.status | json }}</p>
  `
})
export class TechnologyReactiveDetailComponent3 {

  technologyForm: FormGroup; // <--- technologyform is of type formgroup

  constructor(private fb: FormBuilder) { // <--- inject formbuilder
      this.createForm();
   }

  createForm() {
    this.technologyForm = this.fb.group({
        name: ['', Validators.required ]
    });
  }

}

More Form Controls:

import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

import { developer } from './data-model';

@Component({
  selector: 'app-technology-reactive-detail4',
  template: `
        <h2>Technology Reactive Detail</h2>
        <h3><i>FormGroup with multiple FormControls</i></h3>
        <form [formGroup]="technologyForm" novalidate>
              <div class="form-group">
                <label class="center-block">Name:
                    <input class="form-control" formControlName="name">
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Initial Release:
                    <input class="form-control" formControlName="initialRelease">
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Stable Release:
                    <input class="form-control" formControlName="stableRelease">
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Developer:
                  <select class="form-control" formControlName="developer">
                      <option *ngFor="let dev of developer" [value]="dev">{{ dev }}</option>
                  </select>
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Details:
                  <input class="form-control" formControlName="details">
                </label>
              </div>
              <div class="form-group radio">
                  <h4>Super Power:</h4>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="data binding">Data Binding</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="animations">Animations</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="routing">Routing</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="tooling">Tooling</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="beautiful css">Beautiful CSS</label>
              </div>
              <div class="checkbox">
                  <label class="center-block">
                      <input type="checkbox" formControlName="sidekick">I have a sidekick.
                  </label>
              </div>
        </form>
        <p>Form value: {{ technologyForm.value | json }}</p>
  `
})
export class TechnologyReactiveDetailComponent4 {

  technologyForm: FormGroup;
  developer = developer;

  constructor(private fb: FormBuilder) {
      this.createForm();
   }

  createForm() {
    this.technologyForm = this.fb.group({
        name: ['', Validators.required ],
        developer: '',
        initialRelease: '',
        stableRelease: '',
        details: '',
        power: '',
        sidekick: ''
    });
  }

}

Let's have a look at this form in our Chromium Browser:

Nested FormGroups:

import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

import { developer } from './data-model';

@Component({
  selector: 'app-technology-reactive-detail5',
  template: `
        <h2>Technology Reactive Detail</h2>
        <h3><i>Nested FormGroups</i></h3>
        <form [formGroup]="technologyForm" novalidate>
              <div class="form-group">
                <label class="center-block">Name:
                    <input class="form-control" formControlName="name">
                </label>
              </div>
          <div formGroupName="bio" class="card card-body bg-light">
            <h4>Secret Data</h4>
              <div class="form-group">
                <label class="center-block">Initial Release:
                    <input class="form-control" formControlName="initialRelease">
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Stable Release:
                    <input class="form-control" formControlName="stableRelease">
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Developer:
                  <select class="form-control" formControlName="developer">
                      <option *ngFor="let dev of developer" [value]="dev">{{ dev }}</option>
                  </select>
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Details:
                  <input class="form-control" formControlName="details">
                </label>
              </div>
            </div>
              <div class="form-group radio">
                  <h4>Super Power:</h4>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="data binding">Data Binding</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="animations">Animations</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="routing">Routing</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="tooling">Tooling</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="beautiful css">Beautiful CSS</label>
              </div>
              <div class="checkbox">
                  <label class="center-block">
                      <input type="checkbox" formControlName="sidekick">I have a sidekick.
                  </label>
              </div>
        </form>
        <p>Form value: {{ technologyForm.value | json }}</p>
        <h4>Extra info for the curious:</h4>
        <p>Name value: {{ technologyForm.get('name').value }}</p>
        <p>Details value: {{ technologyForm.get('bio.details').value }}</p>
  `
})
export class TechnologyReactiveDetailComponent5 {

  technologyForm: FormGroup;
  developer = developer;

  constructor(private fb: FormBuilder) {
      this.createForm();
   }

  createForm() {
    this.technologyForm = this.fb.group({ // <--- parent formgroup
        name: ['', Validators.required ],
        bio: this.fb.group({ // <-- child formgroup
        developer: '',
        initialRelease: '',
        stableRelease: '',
        details: ''
      }),
        power: '',
        sidekick: ''
    });
  }

}

Populate Form Model with setValue

TechnologyReactiveService:

import { Injectable } from '@angular/core';

import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import 'rxjs/add/operator/delay';

import { Technology, technologies } from './data-model';

@Injectable()
export class TechnologyReactiveService {
  delayMs = 1000;

  getTechnologies(): Observable<Technology[]> {
    return of(technologies).delay(this.delayMs);
  }

  updateTechnology(technology: Technology): Observable<Technology> {
    const oldTechnology = technologies.find(t => t.id === technology.id);
    const newTechnology = Object.assign(oldTechnology, technology);
    return of (newTechnology).delay(this.delayMs);
  }

}

TechnologyReactiveListComponent:

import { Component, OnInit } from '@angular/core';

import { Technology } from './data-model';
import { TechnologyReactiveService } from './technology-reactive.service';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/finally';

@Component({
  selector: 'app-technology-reactive-list',
  template: `
        <h3 *ngIf="isLoading"><i>Loading technologies ...</i></h3>
        <app-technology-reactive-detail7
        *ngFor="let technology of technologies | async"
        [technology]="technology">
        </app-technology-reactive-detail7>
  `
})
export class TechnologyReactiveListComponent implements OnInit {
  technologies: Observable<Technology[]>;
  isLoading = false;

  constructor(private technologyReactiveService: TechnologyReactiveService) { }

  ngOnInit() {
    this.getTechnologies();
  }

  getTechnologies() {
    this.isLoading = true;
    this.technologies = this.technologyReactiveService.getTechnologies()
                        .finally(() => this.isLoading = false);
  }

}

TechnologyReactiveDetailComponent with setValue:

import { Component, Input, OnChanges } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

import { Technology, Descriptions, developer } from './data-model';

@Component({
  selector: 'app-technology-reactive-detail7',
  template: `
        <h3><i>Technology Reactive Secret Details</i></h3>
        <form [formGroup]="technologyForm" novalidate>
              <div class="form-group">
                <label class="center-block">Name:
                    <input class="form-control" formControlName="name">
                </label>
              </div>
          <div formGroupName="bio" class="card card-body bg-light">
            <h4>Secret Data</h4>
              <div class="form-group">
                <label class="center-block">Initial Release:
                    <input class="form-control" formControlName="initialRelease">
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Stable Release:
                    <input class="form-control" formControlName="stableRelease">
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Developer:
                  <select class="form-control" formControlName="developer">
                      <option *ngFor="let dev of developer" [value]="dev">{{ dev }}</option>
                  </select>
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Details:
                  <input class="form-control" formControlName="details">
                </label>
              </div>
            </div>
              <div class="form-group radio">
                  <h4>Super Power:</h4>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="data binding">Data Binding</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="animations">Animations</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="routing">Routing</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="tooling">Tooling</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="beautiful css">Beautiful CSS</label>
              </div>
              <div class="checkbox">
                  <label class="center-block">
                      <input type="checkbox" formControlName="sidekick">I have a sidekick.
                  </label>
              </div>
        </form>
        <p>Form value: {{ technologyForm.value | json }}</p>
  `
})
export class TechnologyReactiveDetailComponent7 implements OnChanges {
  @Input() technology: Technology;

  technologyForm: FormGroup;
  developer = developer;

  constructor(private fb: FormBuilder) {
      this.createForm();
   }

  createForm() {
    this.technologyForm = this.fb.group({
        name: ['', Validators.required ],
        bio: this.fb.group(new Descriptions()), // <-- formgroup with a new descriptions
        power: '',
        sidekick: ''
    });
  }

  ngOnChanges() {
    this.technologyForm.reset();
    this.technologyForm.setValue({
      name: this.technology.name,
      bio: this.technology.descriptions[0] || new Descriptions(),
      power: '',
      sidekick: ''
    });
  }

}

AppComponent with my Polymer Princess(hidden):

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
        <h1>{{ title }}</h1>
          <div class="left">
            <h1>Reactive Forms</h1>
            <app-technology-reactive-list></app-technology-reactive-list>
          </div>
          <div class="right">
            <img [src]="imageUrl" class="img-round" alt="princess image">
          </div>
  `,
  styles: [`h1 { font-weight: bold; }`]
})
export class AppComponent {
  title = 'Tour of Technologies';
  imageUrl = '../assets/polymer6.jpg';

}

Let's have a look at this beauty in Chromium:

patchValue:

  ngOnChanges() {
    this.technologyForm.reset();
    this.technologyForm.patchValue({
      name: this.technology.name,
      bio: this.technology.descriptions[0] || new Descriptions()
    });
  }

FormArray:

import { Component, Input, OnChanges } from '@angular/core';
import { FormArray, FormGroup, FormBuilder, Validators } from '@angular/forms';

import { Technology, Descriptions, developer } from './data-model';

@Component({
  selector: 'app-technology-reactive-detail8',
  template: `
        <h3><i>Technology Reactive Secret Details</i></h3>
        <form [formGroup]="technologyForm" novalidate>
        <p>Form Changed: {{ technologyForm.dirty }}</p>
              <div class="form-group">
                <label class="center-block">Name:
                    <input class="form-control" formControlName="name">
                </label>
              </div>

          <div formArrayName="secretBios" class="card card-body bg-light">
            <div *ngFor="let bios of secretBios.controls; let i = index;" [formGroupName]="i">
            <!-- repeated bios template -->
            <h4>BIOS #{{ i + 1}}</h4>
            <div style="margin-left: 1em;">
              <div class="form-group">
                <label class="center-block">Initial Release:
                    <input class="form-control" formControlName="initialRelease">
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Stable Release:
                    <input class="form-control" formControlName="stableRelease">
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Developer:
                  <select class="form-control" formControlName="developer">
                      <option *ngFor="let dev of developer" [value]="dev">{{ dev }}</option>
                  </select>
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Details:
                  <textarea rows="2" cols="50" class="form-control" formControlName="details"></textarea>
                </label>
              </div>
              <button (click)="removeBios(i)" type="button">Remove Bios</button>
              </div>
              <br>
              <!-- end of repeated bios template -->
            </div>
              <button (click)="addBios()" type="button">Add a Secret Bios</button>
            </div>
              <div class="form-group radio">
                  <h4>Super Power:</h4>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="data binding">Data Binding</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="animations">Animations</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="routing">Routing</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="tooling">Tooling</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="beautiful css">Beautiful CSS</label>
              </div>
              <div class="checkbox">
                  <label class="center-block">
                      <input type="checkbox" formControlName="sidekick">I have a sidekick.
                  </label>
              </div>
        </form>
        <p>technologyForm value: {{ technologyForm.value | json }}</p>
        <h4>Name change log</h4>
        <div *ngFor="let name of nameChangeLog">{{ name }}</div>
  `
})
export class TechnologyReactiveDetailComponent8 implements OnChanges {
  @Input() technology: Technology;

  technologyForm: FormGroup;
  developer = developer;
  nameChangeLog: string[] = [];

  constructor(private fb: FormBuilder) {
      this.createForm();
      this.logNameChange();
   }

  createForm() {
    this.technologyForm = this.fb.group({
        name: ['', Validators.required ],
        secretBios: this.fb.array([]), // <-- secretBios as an empty formarray
        power: '',
        sidekick: ''
    });
  }

  logNameChange() {
    const nameControl = this.technologyForm.get('name');
    nameControl.valueChanges.forEach((value: string) => this.nameChangeLog.push(value));
  }

  ngOnChanges() {
    this.technologyForm.reset({
      name: this.technology.name
    });
    this.setBioses(this.technology.descriptions);
  }

  setBioses(descriptions: Descriptions[]) {
    const biosFGs = descriptions.map(description => this.fb.group(description));
    const biosFormArray = this.fb.array(biosFGs);
    this.technologyForm.setControl('secretBios', biosFormArray);
  }

  get secretBios(): FormArray {
    return this.technologyForm.get('secretBios') as FormArray;
  }

  addBios() {
    this.secretBios.push(this.fb.group(new Descriptions()));
  }

  removeBios(index: number) {
    this.secretBios.removeAt(index);
  }

}

Let's view the running application in Chromium:

Save Form Data:

import { Component, Input, OnChanges } from '@angular/core';
import { FormArray, FormGroup, FormBuilder, Validators } from '@angular/forms';

import { TechnologyReactiveService } from './technology-reactive.service';
import { Technology, Descriptions, developer } from './data-model';

@Component({
  selector: 'app-technology-reactive-detail',
  template: `
        <h3><i>Technology Reactive Secret Details</i></h3>
        <form [formGroup]="technologyForm" (ngSubmit)="onSubmit()" novalidate>
        <div style="margin-bottom: 1em;">
            <button type="submit" [disabled]="technologyForm.pristine" class="btn btn-success">
            Save
            </button> &nbsp;
            <button type="reset" (click)="revert()" [disabled]="technologyForm.pristine"
            class="btn btn-danger">
            Revert
            </button>
        </div>
              <div class="form-group">
                <label class="center-block">Name:
                    <input class="form-control" formControlName="name">
                </label>
              </div>

          <div formArrayName="secretBios" class="card card-body bg-light">
            <div *ngFor="let bios of secretBios.controls; let i = index;" [formGroupName]="i">
            <!-- repeated bios template -->
            <h4>BIOS #{{ i + 1}}</h4>
            <div style="margin-left: 1em;">
              <div class="form-group">
                <label class="center-block">Initial Release:
                    <input class="form-control" formControlName="initialRelease">
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Stable Release:
                    <input class="form-control" formControlName="stableRelease">
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Developer:
                  <select class="form-control" formControlName="developer">
                      <option *ngFor="let dev of developer" [value]="dev">{{ dev }}</option>
                  </select>
                </label>
              </div>
              <div class="form-group">
                <label class="center-block">Details:
                  <textarea rows="2" cols="50" class="form-control" formControlName="details"></textarea>
                </label>
              </div>
              <button (click)="removeBios(i)" type="button">Remove Bios</button>
              </div>
              <br>
              <!-- end of repeated bios template -->
            </div>
              <button (click)="addBios()" type="button">Add a Secret Bios</button>
            </div>
              <div class="form-group radio">
                  <h4>Super Power:</h4>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="data binding">Data Binding</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="animations">Animations</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="routing">Routing</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="tooling">Tooling</label>
                  <label class="center-block"><input type="radio" formControlName="power"
                  value="beautiful css">Beautiful CSS</label>
              </div>
              <div class="checkbox">
                  <label class="center-block">
                      <input type="checkbox" formControlName="sidekick">I have a sidekick.
                  </label>
              </div>
        </form>
        <p>technologyForm value: {{ technologyForm.value | json }}</p>
        <h4>Name change log</h4>
        <div *ngFor="let name of nameChangeLog">{{ name }}</div>
  `
})
export class TechnologyReactiveDetailComponent implements OnChanges {
  @Input() technology: Technology;

  technologyForm: FormGroup;
  developer = developer;
  nameChangeLog: string[] = [];

  constructor(private fb: FormBuilder, private technologyReactiveService: TechnologyReactiveService) {
      this.createForm();
      this.logNameChange();
   }

  createForm() {
    this.technologyForm = this.fb.group({
        name: ['', Validators.required ],
        secretBios: this.fb.array([]), // <-- secretBios as an empty formarray
        power: '',
        sidekick: ''
    });
  }

  logNameChange() {
    const nameControl = this.technologyForm.get('name');
    nameControl.valueChanges.forEach((value: string) => this.nameChangeLog.push(value));
  }

  ngOnChanges() {
    this.technologyForm.reset({
      name: this.technology.name
    });
    this.setBioses(this.technology.descriptions);
  }

  setBioses(descriptions: Descriptions[]) {
    const biosFGs = descriptions.map(description => this.fb.group(description));
    const biosFormArray = this.fb.array(biosFGs);
    this.technologyForm.setControl('secretBios', biosFormArray);
  }

  get secretBios(): FormArray {
    return this.technologyForm.get('secretBios') as FormArray;
  }

  addBios() {
    this.secretBios.push(this.fb.group(new Descriptions()));
  }

  removeBios(index: number) {
    this.secretBios.removeAt(index);
  }

  revert() {
    this.ngOnChanges();
  }

  onSubmit() {
    this.technology = this.prepareSaveTechnology();
    this.technologyReactiveService.updateTechnology(this.technology).subscribe(/* error handling*/);
    this.ngOnChanges();
  }

  prepareSaveTechnology(): Technology {
    const formModel = this.technologyForm.value;

    // deep copy of form model bios
    const secretBiosDeepCopy: Descriptions[] = formModel.secretBios.map(
      (description: Descriptions) => Object.assign({}, description)
    );

    // return new technology object containing a combination of original technology value(s)
    // and deep copies of changed form model values

    const saveTechnology: Technology = {
      id: this.technology.id,
      name: formModel.name as string,
      descriptions: secretBiosDeepCopy
    };
    return saveTechnology;
  }

}

Let's view the results and test the app in our Chromium Web Browser:

LoginComponent:

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators, AbstractControl, ValidationErrors } from '@angular/forms';

export class UserNameValidators {
      static cannotContainSpace(control: AbstractControl): ValidationErrors | null {
            if ((control.value as string).indexOf(' ') !== -1) {
                return { cannotContainSpace: true };
            }
            return null;
      }
      static shouldBeUnique(control: AbstractControl): Promise<ValidationErrors | null> {
            return new Promise((resolve, reject) => {
              setTimeout(() => {
                if (control.value === 'nils-holger') {
                  resolve({ shouldBeUnique: true });
                } else {
                resolve(null);
                }
              }, 1500);
            });
      }
}

@Component({
  selector: 'app-login',
  template: `
  <form [formGroup]="form" (ngSubmit)="login()">
      <div *ngIf="form.errors" class="alert alert-danger">
              Username and/or password are invalid.
      </div>
      <div formGroupName="account">
      <div class="form-group">
        <label for="username">Username:</label>
        <input formControlName="username" id="username" type="text" class="form-control">
        <div *ngIf="username.pending">
          <i class="fa fa-spinner fa-pulse fa-lg fa-fw"></i>
          <span class="sr-only">Loading...</span>
        </div>
        <div *ngIf="username.touched && username.invalid"
              class="alert alert-danger">
              <div *ngIf="username.errors.required">Username is required.</div>
              <div *ngIf="username.errors.minlength">
              Username should be minimum {{ username.errors.minlength.requiredLength }} characters.
              </div>
              <div *ngIf="username.errors.cannotContainSpace">Username cannot contain space.</div>
              <div *ngIf="username.errors.shouldBeUnique">Username is already taken.</div>
       </div>
      </div>
        <div class="form-group">
          <label for="password">Password:</label>
          <input formControlName="password" id="password" type="password" class="form-control">
          <div *ngIf="password.pending">
          Checking password ...
          </div>
          <div *ngIf="password.touched && password.invalid"
                class="alert alert-danger">
                <div *ngIf="password.errors.required">Password is required.</div>
                <div *ngIf="password.errors.minlength">
                Password should be minimum {{ password.errors.minlength.requiredLength }} characters.
                </div>
                <div *ngIf="password.errors.cannotContainSpace">Password cannot contain space.</div>
                <div *ngIf="password.errors.shouldBeUnique">Password is already taken.</div>
         </div>
        </div>
      </div>
        <button class="btn btn-primary" [disabled]="!form.valid" type="submit">Log In</button>
  </form>
  `,
  styles: [`
    .form-control.ng-touched.ng-invalid {
      border-left: 5px solid red;
    }
    .form-control.ng-touched,ng-dirty.ng-valid {
      border-left: 5px solid green;
    }
  `]
})
export class LoginComponent {
      form = new FormGroup({
        account: new FormGroup({
        username: new FormControl('', [
          Validators.required,
          Validators.minLength(5),
          UserNameValidators.cannotContainSpace
        ], UserNameValidators.shouldBeUnique),
        password: new FormControl('', [
          Validators.required,
          Validators.minLength(7),
          UserNameValidators.cannotContainSpace
        ], UserNameValidators.shouldBeUnique)
      })
      });

      get username() {
        return this.form.get('account.username');
      }

      get password() {
        return this.form.get('account.password');
      }

      login() {
        this.form.setErrors({
          invalidLogin: true
        });
      }

}

Let's view this form in our Chromium Web Browser:

NewTechnologyFormComponent:

import { Component } from '@angular/core';
import { FormGroup, FormArray, FormControl } from '@angular/forms';

@Component({
  selector: 'app-new-technology-form-component',
  template: `
            <form>
                  <input #box
                          type="text"
                          class="form-control"
                          (keyup.enter)="add(box)">
                  <ul class="list-group">
                    <li *ngFor="let component of components.controls;
                                first as isFirst; last as isLast;
                                even as isEven; odd as isOdd;"
                         (click)="remove(component)"
                         class="list-group-item list-group-item-action"
                         [class.list-group-item-danger]="isEven && !isFirst && !isLast"
                         [class.list-group-item-warning]="isOdd && !isFirst && !isLast"
                         [class.list-group-item-primary]="isFirst"
                         [class.list-group-item-success]="isLast">
                         {{ component.value }}
                    </li>
                  </ul>
            </form>
  `
})
export class NewTechnologyFormComponent {

            form = new FormGroup({
              components: new FormArray([])
            });

            add(component: HTMLInputElement) {
              if (component.value) {
              this.components.push(new FormControl(component.value));
              component.value = '';
             }
            }

            remove(component: FormControl) {
              const idx = this.components.controls.indexOf(component);
              this.components.removeAt(idx);
            }

            get components() {
              return this.form.get('components') as FormArray;
            }

}

Let's view the results in Chromium:

FormBuilder:

export class NewTechnologyFormComponentComponent {
            form;

            constructor(fb: FormBuilder) {
              fb.group({
                name: ['', Validators.required],
                contact: fb.group({
                  email: [],
                  phone: []
                }),
                components: fb.array([])
              });
            }

}

ChangePasswordComponent:

import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators, AbstractControl, ValidationErrors } from '@angular/forms';

export class ChangePasswordValidators {

      static shouldEqual(control: AbstractControl): Promise<ValidationErrors | null> {
            return new Promise((resolve, reject) => {
              setTimeout(() => {
                if (control.value !== '12345') {
                  resolve({ shouldEqual: true });
                } else {
                resolve(null);
                }
              }, 1500);
            });
      }

      static shouldMatch(control: AbstractControl)  {
            const newPassword = control.get('newpassword');
            const confirmPassword = control.get('confirmpassword');

            if (newPassword.value !== confirmPassword.value) {
              return { shouldMatch: true };
            }
            return null;
      }
}

@Component({
  selector: 'app-change-password',
  template: `
  <form [formGroup]="form" (ngSubmit)="change()">
      <div class="form-group">
        <label for="oldpassword">Old Password</label>
        <input formControlName="oldpassword" type="password" class="form-control">
        <div *ngIf="oldpassword.pending">
            Validating Password ...
        </div>
        <div *ngIf="oldpassword.touched && oldpassword.invalid"
              class="alert alert-danger">
              <div *ngIf="oldpassword.errors.required">Old Password is required.</div>
              <div *ngIf="oldpassword.errors.shouldEqual">Old Password is invalid.</div>
       </div>
      </div>
        <div class="form-group">
          <label for="newpassword">New Password</label>
          <input formControlName="newpassword" type="password" class="form-control">
          <div *ngIf="newpassword.touched && newpassword.invalid"
                class="alert alert-danger">
                <div *ngIf="newpassword.errors.required">New Password is required.</div>
         </div>
        </div>
        <div class="form-group">
          <label for="confirmpassword">Confirm Password</label>
          <input formControlName="confirmpassword" type="password" class="form-control">
          <div *ngIf="confirmpassword.touched && confirmpassword.invalid"
                class="alert alert-danger">
                <div *ngIf="confirmpassword.errors.required">Confirm Password is required.</div>
         </div>
         <div *ngIf="confirmpassword.valid && form.invalid && form.errors.shouldMatch" class="alert alert-danger">
                    Passwords don\'t match.
        </div>
        </div>
        <button class="btn btn-primary" [disabled]="!form.valid" type="submit">Change</button>
  </form>
  `,
  styles: [`
    .form-control.ng-touched.ng-invalid {
      border-left: 5px solid red;
    }
    .form-control.ng-touched,ng-dirty.ng-valid {
      border-left: 5px solid green;
    }
  `]
})
export class ChangePasswordComponent {
      form: FormGroup;

      constructor(fb: FormBuilder) {
            this.form = fb.group({
              oldpassword: ['', Validators.required, ChangePasswordValidators.shouldEqual],
              newpassword: ['', Validators.required],
              confirmpassword: ['', Validators.required]
            },
              {
                validator: ChangePasswordValidators.shouldMatch
              }
          );
      }

      get oldpassword() {
        return this.form.get('oldpassword');
      }

      get newpassword() {
        return this.form.get('newpassword');
      }

      get confirmpassword() {
        return this.form.get('confirmpassword');
      }

      change() {
        // do something here
      }

}

results matching ""

    No results matching ""