FORMS

html forms illustrated

Angular includes two types of forms

  1. Template Driven Forms (see Backup slides)
  2. Model Driven Forms (reactive)

The underlying data model / API is nearly the same - it's about where the data model is defined - either the view or the component class.

Model driven forms (reactive)

  • are written by code
  • are easily testable
  • are strictly typed
const contactForm = new FormGroup<ContactFormType>({
  name: new FormGroup<NameFormType>({
    first: new FormControl('Juana'),
    last: new FormControl('Garcia')
  }),
  email: new FormControl('')
});
// types used above
interface ContactFormType {
  name: FormGroup<NameFormType>;
  email: FormControl<string | null>;
}
type NameFormType = {
  first: FormControl<string | null>;
  last: FormControl<string | null>;
};

... and can be bound by

<form [formGroup]="contactForm" novalidate>
  <div formGroupName="name">
    <div>
      <label>First Name</label>
      <input type="text" formControlName="first">
    </div>
    <div>
      <label>Last Name</label>
      <input type="text" formControlName="last">
    </div>
  </div>
  <div>
    <label>E-Mail</label>
    <input type="email" formControlName="email">
  </div>
  <button type="submit">Submit</button>
</form>
  • [formGroup]="contactForm" binds upper FormGroup
  • formGroupName binds child FormGroup
  • formControlName binds child FormControl

FormBuilder

... creates the same FormGroup-FormControl tree

interface ContactFormType {
  name: FormGroup<NameFormType>;
  email: FormControl<string | null>;
}
type NameFormType = {
  first: FormControl<string | null>;
  last: FormControl<string | null>;
};

@Component({ ... })
export class FormTestComponent implements OnInit {
  contactForm: FormGroup;

  constructor(private formBuilder: FormBuilder) { }

  ngOnInit(): void {
	  contactForm = this.formBuilder.group({
	    name: this.formBuilder.group({
	      first: "Juana",
	      last: "Garcia",
	    }),
	    email: "",
	  });
  }
}

Validation

... in Model Driven forms

  this.contactForm = this.formBuilder.group({
    name: this.formBuilder.group({
      first: "Juana",
      last: ["Garcia", Validators.required],
    }),
    email: ["", Validators.email],
  });
  • FormControl and FormGroup both extend AbstractControl
  • ... which has properties valid, invalid, errors, etc.
  • valueChanges and statusChanges provide Observables
  • FormControl has single controls status
  • FormGroup has aggregated status of child controls

Custom Validation Interface

interface ValidatorFn {
    (c: AbstractControl): {
        [key: string]: any;
    } | null;
}

ValidatorFn takes an AbstractControl to validate.

Returns null if valid.

Returns {[key: string]: any} as error definition.

Custom Validation Example

Higher order function can return ValidatorFn

which returns { forbiddenName: name } as error.

function forbiddenName(forbiddenName: string): ValidatorFn {
  return (control: AbstractControl): ValidationErrors => {
    const name = control.value;
    const isForbidden = forbiddenName === name;
    return isForbidden ? { forbiddenName: name } : null;
  };
}

Displaying (Custom) Validation

behaves just like Angular Validators

FormControl.errors is extended by custom error key-value pairs from validation method.

<div 
  *ngIf="contactForm.controls.name.controls.first.getError('forbiddenName')">
  The name is not allowed.
</div>

Summary

  • Model Driven Forms:
    • separate view from typed model
    • Model Driven Forms
  • Forms can be validated using
    • default Validators provided by Angular
    • defining Custom Validator for Field

Live Code Examples & Backup slides below

Live Code Examples

Backup slides

Following slides are not subject of the lecture.
Ask trainers in case of questions.

Template driven forms

  • behave like Angular 1 forms
  • form model is defined inside HTML template
<form #contactForm="ngForm" (ngSubmit)="submit(contactForm.value)">
	  <div ngModelGroup="name">
		<label for="first">First Name</label>
		<input type="text" name="first" ngModel>

		<label for="last">Last Name</label>
		<input type="text" name="last" ngModel>
	  </div>

	  <label for="email">E-Mail</label>
	  <input type="text" name="email" ngModel>

	  <button type="submit">Submit</button>
	</form>
	
{ name: { first: string, last: string }, email:string }

ngModel / ngModelGroup

<input name=first ngModel>

creates property inside forms data model with key 'first'

<input name=first [ngModel]="varName">

creates one-way binding - binds a default value


	<input name=first [(ngModel)]="varName">
	<input name=first [ngModel]="varName" (ngModelChange)="varName=$event">

creates two-way binding. Note: state at two places (component member and forms model)

<div ngModelGroup=group></div>

creates nested object called group inside forms data model (form.value)

Validation

... in Template Driven forms

  • Angular provides default validators
    <input name=otto ngModel required minlength=4 maxlength=24>
  • Each control / property has 'errors' which can be checked
    <form #form=ngForm>
    	  <input name=otto ngModel required>
    	  <div *ngIf=form.controls.otto.errors>
    		<div [hidden]="!form.controls.otto.errors.required">
    			<span>Otto is really important!</span>
    		</div>
    		
    	  </div>
    	</form>
  • form and ngModelGroup aggregate underlying controls (combined errors)

Bookmarks

  • Angular Forms Angular Forms
  • Angular NgModelGroup Directive Angular NgModelGroup Directive
  • Template reference variables Template reference variables
  • Form Builder Form Builder
  • Custom Form Validators Custom Form Validators
  • Abstract Control Abstract Control
  • Directive Directive
  • Form Control Form Control
  • Typed Forms Typed Forms
  • Directive Validator

    Adding custom validators to Template Driven forms via Directives

    <input type="text" name="first" ngModel noForbiddenName>
    1. Create directive with matching selector - e.g. 'noForbiddenName'
    2. Extend NG_VALIDATORS dependency via providers property (directive meta data)
    3. Implement validate() for directive and call validator - e.g. forbiddenNameValidator

    Directive Validator example

    @Directive({
    	  selector: '[noForbiddenName][ngModel],[noForbiddenName][formControl],[noForbiddenName][formControlName]',
    	  providers: [{ 
    			provide: NG_VALIDATORS, 
    			useExisting: forwardRef(() => ForbiddenNameValidator), 
    			multi: true 
    	  }]
    	})
    	export class ForbiddenNameValidator implements Validator {
    
    	  validator = forbiddenNameValidatorFn;
    
    	  validate(c: FormControl) {
    		return this.validator(c);
    	  }
    	}