A starter example

This example takes us to where we were previously but with the editor in a separate component that sends an event back to the parent component when the edit is complete.

editor.component.html
<form [formGroup]="profile" (ngSubmit)="onSubmit()">
	<div><label>Name: <input type="text"  formControlName="name"></label></div>
	<div><label>Age: <input type="text"   formControlName="age"></label></div>
	<div><label>Phone: <input type="text" formControlName="phone"></label></div>
	<div><label>Email: <input type="text" formControlName="email"></label></div>
	<div><button class="btn btn-primary" type="submit">Save</button></div>
</form>
editor.component.ts
import { Component, OnInit, Input, EventEmitter, Output, OnChanges } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { Person } from '../../models/person';

@Component({
  selector: 'app-editor',
  templateUrl: './editor.component.html',
  styleUrls: ['./editor.component.css']
})
export class EditorComponent implements OnInit {
	@Input() person: Person;
	@Output() finishedEditing = new EventEmitter<boolean>(); (0)
	profile = new FormGroup({
		name: new FormControl(''), (1)
		age: new FormControl(''),
		phone: new FormControl(''),
		email: new FormControl(''),
	});

  constructor() { }

  ngOnInit() {
  }

  onSubmit() {
	  this.person.name = this.profile.value.name; (2)
	  this.person.age = this.profile.value.age;
	  this.person.contact.phone = this.profile.value.phone;
	  this.person.contact.email = this.profile.value.email;
	  this.finishedEditing.emit(true);
  }

  ngOnChanges() {
	  this.profile.patchValue({ (3)
		  name: this.person.name,
		  age: this.person.age,
		  phone: this.person.contact.phone,
		  email: this.person.contact.email,
	  });
  }

}
1 EventEmitters (imported above) send events out. The parent component will have an attribute binding like: (finishedEditing)="onFinishedEditing()"
2 The default value for the control is the first argument. We don’t have that value yet so we must change the value when we notice that the person input value has changed.
3 When we have finished editing (assuming the results are valid), we get the values out of the FormControls and update the input model with them. This will keep the data structures separate!
4 patchValue is used to mass-update the FormGroup. We have noticed a change in person so we will update all of the element values.

Validation

import { Validators } from '@angular/forms';

You can access the status information in your form’s status property. e.g. {{ myForm.status }} in either the component or the template. You can add validators from the Validators package to any FormControl by passing it into the constructor after the default value as a single validator or an array of validators.

Required

Required is a built-in validation property in HTML5, but it also exists in the Validators package.

minLength/maxLength

similarly minlength exists in both HTML5 and Angular forms

email

Check for an email

pattern

Custom regex

requiredTrue

Check that a checkbox is checked

custom validators

Custom validators can be created to match anything not in the Validators package: Custom Validators

A full list of validators: Validators

Observe a new .ts/.html file set:

editor.component.html
<form [formGroup]="profile" (ngSubmit)="onSubmit()">
	<div><label>Name: <input type="text"  formControlName="name" required></label></div> (0)
	<div [hidden]="profile.controls.name.valid || profile.controls.name.pristine" (1)
       class="alert alert-danger"> (2)
	  Name is required.
	</div>
	<div><label>Age: <input type="text"   formControlName="age"></label></div>
	<div [hidden]="profile.controls.age.valid || profile.controls.age.pristine"
       class="alert alert-danger">
	  You must be at least 18.
	</div>
	<div><label>Phone: <input type="text" formControlName="phone"></label></div>
	<div [hidden]="profile.controls.phone.valid || profile.controls.phone.pristine"
       class="alert alert-danger">
	  You must provide a valid US phone number.
	</div>
	<div><label>Email: <input type="text" formControlName="email" requried></label></div>
	<div [hidden]="profile.controls.email.valid || profile.controls.email.pristine"
       class="alert alert-danger">
	  You must provide a valid email address.
	</div>
	<div><button class="btn btn-primary" type="submit" [disabled]="!profile.valid">Save</button></div> (3)
</form>
1 required tag is an HTML5 special
2 we wish to ensure that the name control is either valid or has not yet been changed
3 These classes exist in bootstrap to show messages
4 Note that we do not want the submit button to be enabled unless the entire form is valid
editor.component.ts
import { Component, OnInit, Input, EventEmitter, Output, OnChanges } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { Person } from '../../models/person';
import { Validators } from '@angular/forms';

@Component({
  selector: 'app-editor',
  templateUrl: './editor.component.html',
  styleUrls: ['./editor.component.css']
})

export class EditorComponent implements OnInit {
	@Input() person: Person;
	@Output() finishedEditing = new EventEmitter<boolean>();
	profile = new FormGroup({
		name: new FormControl('', [Validators.required, Validators.minLength(3)]), (0)
		age: new FormControl('', [Validators.min(18), Validators.max(110)]),
		phone: new FormControl('', Validators.pattern(/^\(?([0-9]{3})\)?[-.●]?([0-9]{3})[-.●]?([0-9]{4})$/)), (1)
		email: new FormControl('', [Validators.email, Validators.required]),
	});

  constructor() { }

  ngOnInit() {
  }

  onSubmit() {
	  if (this.profile.valid) { (2)
		  this.person.name = this.profile.value.name;
		  this.person.age = this.profile.value.age;
		  this.person.contact.phone = this.profile.value.phone;
		  this.person.contact.email = this.profile.value.email;
		  this.finishedEditing.emit(true);
	  }
  }

  ngOnChanges() {
	  this.profile.patchValue({
		  name: this.person.name,
		  age: this.person.age,
		  phone: this.person.contact.phone,
		  email: this.person.contact.email,
	  });
  }

}
1 Arrays of validators mean all conditions must be met
2 This is a custom regex pattern validator. You can validate almost anything this way if you know regex!
3 We do not want to allow the user to update any information (especially backend) unless they have already passed the valid state. This is sort of a fallback for if they submit the form in some other way than our disabled button.
Questions
  • How is this approach better than the "wild west" style that we looked at last week?

  • Is there more validation that we should be checking?

  • What happens when the user uses the javascript console to submit the form anyway?

Source for this version of studentr available: resources/profile-app.zip