Thi's avatar
HomeAboutNotesBlogTopicsToolsReading
About|My sketches |Cooking |Cafe icon Support Thi
πŸ’Œ [email protected]

Note for course Angular 4: Forms

Anh-Thi Dinh
AngularMOOCmooc-angular
Left aside

TIPS

πŸ‘‰ Assignment example of using Forms in Angular (video)+ codes.
πŸ‘‰ Project using Form :
codes.

Why Forms in Angular?

Because your app is SVC (single-view-component) β†’ when you submit a form, how can web do it? β†’ that's why angular need a special "things" for forms.
  • Get input values by the users
  • Check the valid of input
  • Styling conditionally the form
Left: HTML form β†’ Right: key-value object can be submitted with the form.

2 approaches with forms

  • Template driven ← inferred from DOM (html)
  • Reactive ← form created programmatically and synchronized with the DOM β†’ more controls

Template-driven form

πŸ‘‰ Codes for this section.
Everything you wanna do β†’ do it in the template ← template-driven!!!!

Create a TD form

The form here don't make any HTML request. ← <form> has no attribute!
Angular doesn't recognize auto elements of <form> (label, input,...) because there may be some inputs which aren't served when submitting a form (their functions are different from a function of a form but they're inside <form>) β†’ ==need to tell angular which ones to be extra controlled?==

Submitting & Using the Form

πŸ‘‰ Codes for this section.
We can actually see what users entered.
Get the values user entered: form.value

Form State

  • form.controls β†’ several properties to control the form.
  • form.dirty: true if we changed something in the form.
  • form.invalid / form.valid: if we add some invalidator / validator (to check the fields), this can be true or false.
  • Read more in official doc.

Access Form via @ViewChild()

πŸ‘‰ Codes for this section.
With #f (local ref), we can use @ViewChild. β‡’ Useful when you wanna access the form not only at the time you submit it but also earlier!

Validation

πŸ‘‰ Codes for this section.
πŸ‘‰ List of all built-in validators.
πŸ‘‰
List of directives which can be added to your template.
There are 2 places for .valid information ← form.valid and form.controls.email.valid
When it's invalid (after clicking on submit) or valid β†’ angular auto add classes to the html element ← we can use these class to style our element!
Enable HTML5 validation (by default, Angular disables it) β†’ ngNativeValidate

Set default values

πŸ‘‰ Codes for this section.
Using 1-way binding / property binding ([ngModel]) to set the default value.

Instantly react to changes

Before, the check only performed after clicking "Submit" β†’ If you wanna check "lively" β†’ Using two-way binding [(NgModel)]

Binding with NgModel

  • 0-way binding, NgModel β†’ tell angular that this input is a control
  • 1-way binding, [NgModel] β†’ get this control a default value
  • 2-way binding, [(NgModel)] β†’ Instantly out / do whatever you want with that value

Grouping Form Controls

In a big form, we need to "group" some values into a group / validate some specific group of inputs.
After submit: instead of getting form.value.email, but getting form.value.userData.email (and also form.controls.userData)

Radio buttons

πŸ‘‰ Codes for this section.

Set values to input fields

(video) With this HTML file

Using Form Data

πŸ‘‰ Codes for this section.

Reactive Form

Create the form programmatically (not from scratch).
πŸ‘‰ Codes for this section + Video.

Setting up

πŸ‘‰ Codes for this section.
  • FormGroup β†’ a form, in the end, it's just a group of controls.
  • We don't need FormsModule in app.module.ts (it's for template-driven form) β†’ NEED ReactiveFormsModule
  • We don't need local reference anymore.
  • We configure all things in the typescript code (component.ts)

Grouping Controls

πŸ‘‰ Codes for this section.
FormGroup inside FormGroup.

FormArray

πŸ‘‰ Codes for this section.
Let the user dynamically add their form controls (we don't know yet there are how many controls there) β†’ using an array of form.
Get access to FormArray

Validation

πŸ‘‰ Codes for this section.
Get access directly to the FormControl using .get()

Custom validation

πŸ‘‰ Codes for this section.
Suppose that we don't want users use some specific names.

Custom async validation

πŸ‘‰ Codes for this section.
(video) Suppose we wanna check "already-taken" emails from the server.

Form Status: statusChanges, valueChanges

.statusChanges β†’ gives the status of the form (INVALID, VALID, PENDING,...)
.valueChanges β†’ change something in the form (eg. typing something).

Set values to input fields

Reset the form,
β—†TIPSβ—†Why Forms in Angular?β—†2 approaches with formsβ—†Template-driven formβ—‹Create a TD formβ—‹Submitting & Using the Formβ—‹Form Stateβ—‹Access Form via @ViewChild()β—‹Validationβ—‹Set default valuesβ—‹Instantly react to changesβ—‹Binding with NgModelβ—‹Grouping Form Controlsβ—‹Radio buttonsβ—‹Set values to input fieldsβ—‹Using Form Dataβ—†Reactive Formβ—‹Setting upβ—‹Grouping Controlsβ—‹FormArrayβ—‹Validationβ—‹Custom validationβ—‹Custom async validationβ—‹Form Status: statusChanges, valueChangesβ—‹Set values to input fields
About|My sketches |Cooking |Cafe icon Support Thi
πŸ’Œ [email protected]
1// make sure
2// app.module.ts
3import { FormsModule } from '@angular/forms';
4
5@NgModule({
6	imports: [
7		FormsModule // with this, angular will auto create form based on <form> in html
8	]
9})
10export ...
1<!-- app.component.ts -->
2<form>
3	<input type="text">        <!-- normal input (without "ngModel") -->
4	<input                     <!-- tell angular to control this input -->
5		type="text"
6		ngModel   <!-- looks like 2-way binding, ie. [(ngModel)] -->
7		name="username">  <!-- must have <- registered in JS representation of the form -->
8</form>
1// If we use normal form
2<form>
3	<button type="submit">Submit</button> // normal behavior - sending http request
4</form>
1// We use ngSubmit
2<form (ngSubmit)="onSubmit(f)" #f="ngForm">
3														//   ^Hey, get me access to this form you created
4														//        automatically
5	<button type="submit">Submit</button>
6</form>
1// app.component.ts
2onSubmit(form: NgForm) {
3	console.log(form);
4	// Get the values user entered: form.value
5}
1// .html
2<form (ngSubmit)="onSubmit(f)" #f="">...</form>
3
4// .component.ts
5onSubmit(form: HTMLFormElement) {
6	console.log(form);
7}
1// .html
2<form (ngSubmit)="onSubmit()" #f="ngForm">...</form>
3											//  ^don't have "f" here
4
5// .component.ts
6export class ... {
7	@ViewChild('f') signupForm: NgForm;
8
9	onSubmit() {
10		console.log(this.signupForm);
11	}
12
1<form (ngSubmit)="onSubmit()" #f="ngForm">
2	<input
3		type="text"
4		ngModel
5		name="username"
6		required>
7<!--    ^default HTML attribute <- angular see it as a built-in directive -->
8
9	<input
10		type="email"
11		ngModel
12		required
13		email  // angular's directive, not html attribute -> make sure it's a valid email
14		#email="ngModel">
15		   <!-- ^ expose some additional info abt the controls -->
16
17	<span *ngIf="!email.valid && email.touched">Please enter valid email!</span>
18
19	<button
20		type="submit"
21		[disabled]="!f.valid">Submit</button>
22</form>
1// if user touched in input and leave
2//   it but it's invalid
3input.ng-invalid.ng-touched{
4	...
5}
1// patterns (eg. number > 0)
2<input
3	type="number"
4	name="amount"
5	ngModel
6	pattern="^[1-9]+[0-9]*$"
7>
1<select
2  id="secret"
3  class="form-control"
4  [ngModel]="defaultQuestion"
5  name="secret">
6  <option value="pet">Your first Pet?</option>
7  <option value="teacher">Your first teacher?</option>
8</select>
9
10// component.ts
11defaultQuestion = "pet";
1<div class="form-group">
2  <textarea
3    name="questionAnswer"
4    rows="3"
5    class="form-control"
6    [(ngModel)]="answer"></textarea>
7</div>
8<p>Your reply: {{ answer }}</p>
1<div
2	ngModelGroup="userData" <!--  ^the key name for this group -->
3	#userData="NgModelGroup">
4	<!-- input fields -->
5</div>
6<p *ngIf="!userData.valid && userData.touched">User data is invalid!</p>
1<div class="radio" *ngFor="let gender of genders">
2  <label>
3    <input
4      type="radio"
5      name="gender"
6      ngModel
7      [value]="gender"
8      required>
9    {{ gender }}
10  </label>
11</div>
12
13<!-- .component.ts -->
14genders = ['male', 'female'];
1// .component.ts
2this.signupForm.setValue({
3	userData: {
4	  username: suggestedName,
5	  email: ''
6	},
7	secret: 'pet',
8	questionAnswer: '',
9	gender: 'male'
10});
11// down side -> overwrite all fields whenever we click "Suggest an Username".
12
13// If we wanna set value TO A SINGLE ONE FIELD?
14this.signupForm.form.patchValue({
15						//  ^patchValue is only available with .form
16  userData: {
17    username: suggestedName
18  }
19});
1// html
2<div *ngIf="submitted">
3  <h3>Your Data</h3>
4  <p>Username: {{ user.username }}</p>
5  <p>Mail: {{ user.email }}</p>
6  <p>Secret Question: Your first {{ user.secretQuestion }}</p>
7  <p>Answer: {{ user.answer }}</p>
8  <p>Gender: {{ user.gender }}</p>
9</div>
1// .component.ts
2export class AppComponent {
3	user = {
4    username: '',
5    email: '',
6    secretQuestion: '',
7    answer: '',
8    gender: ''
9  };
10  submitted = false;
11
12	onSubmit() {
13    this.submitted = true;
14    this.user.username = this.signupForm.value.userData.username;
15    this.user.email = this.signupForm.value.userData.email;
16    this.user.secretQuestion = this.signupForm.value.secret;
17    this.user.answer = this.signupForm.value.questionAnswer;
18    this.user.gender = this.signupForm.value.gender;
19
20    this.signupForm.reset(); // to reset the form
21  }
22}
1// app.module.ts
2import { ReactiveFormsModule } from '@angular/forms';
3
4@NgModule({
5  imports: [
6    ReactiveFormsModule
7  ]
8})
9export ...
1// html
2<form [formGroup]="signupForm">
3//     ^hey, don't treat this form as normal or create form for me, just
4//        use my formgroup "signupForm" (in component.ts)
5	<input
6		formControlName="username">
7//  ^the name given in component.ts of this input field
8</form>
1// app.component.ts
2export ... OnInit {
3	signupForm: FormGroup;
4
5	ngOnInit() {
6		this.signupForm = new FormGroup({
7      'username': new FormControl(null);
8		// |      				|				    ^initial value
9		// |              ^each field is a FormControl
10		// ^the same name for "formControlName" in .html <- that's the link
11    });
12	}
13}
1// .component.ts
2this.signupForm = new FormGroup({
3  'userData': new FormGroup({
4    'username': new FormControl(...),
5    'email': new FormControl(...)
6  }),
7  'gender': new FormControl('male'),
8});
1// html
2// need to put 'username' and 'email' inside a div
3<form [formGroup]="signupForm">
4	<div formGroupName="userData">
5		<input formControlName="username">
6
7		<span *ngIf="!signupForm.get('userData.username').valid>
8															//  ^new here
9			Please enter a valid username!
10		</span>
11
12		<input formControlName="email">
13	</div>
14</form>
1// .component.ts
2this.signupForm = new FormGroup({
3  ...
4  'hobbies': new FormArray([])
5});
1// html
2<div formArrayName="hobbies">
3  <button
4    (click)="onAddHobby()">Add Hobby</button>
5  <div
6    *ngFor="let hobbyControl of signupForm.get('hobbies').controls; let i = index">
7    <input type="text" class="form-control" [formControlName]="i">
8  </div>
9</div>
1// 1st way
2// .ts
3getControls() {
4	return (<FormArray>this.signupForm.get('hobbies')).controls;
5}
6
7// .html
8*ngFor="let hobbyControl of getControls(); let i = index"
1// 2nd way (using getter)
2// .ts
3get controls() {
4	return (this.signupForm.get('hobbies') as FormArray).controls;
5}
6
7// .html
8*ngFor="let hobbyControl of controls; let i = index"
1this.signupForm = new FormGroup({
2  'username': new FormControl(null, Validators.required);
3																					//   ^it's actually .required() but in
4																					//    this case, we wanna add a ref
5																					//    to this method, angular'll know
6																					//    to call it whenever we make changes
7	'email': new FormControl(null, [Validators.required, Validators.email]);
8															// ^multiple validations
9});
1<span
2	*ngIf="!signupForm.get('username').valid && signupForm.get('username').touched">
3	Please enter a valid username!
4</span>
1// for the overall form
2<span
3	*ngIf="!signupForm.valid && signupForm.touched">
4	Please enter valid data!
5</span>
1// .ts
2forbiddenUsernames = ['Chris', 'Anna'];
3
4ngOnInit() {
5	this.signupForm = new FormGroup({
6		'username': new FormControl(
7			null, [
8				Validators.required,
9				this.forbiddenNames.bind(this)]),
10											//   ^need this 'cause angular will call .forbiddenNames()
11											//    (not current class -> cannot use only "this." directly
12											//    -> let angular knows (binds) to current class as "this"
13	}
14}
15
16forbiddenNames(control: FormControl): {[s: string]: boolean} {
17																		// ^a return type
18  if (this.forbiddenUsernames.indexOf(control.value) !== -1) {
19																							//     ^return of .indexOf
20																							//      (if not contains) is "-1"
21    return {'nameIsForbidden': true};
22  }
23  return null;
24		//   ^we don't return ... "false" here because for angular, returning null
25		//      means "valid"
26}
1// use error message with "nameIsForbidden"
2// can use Inspect of the browser to check the location of "nameISForbidden"
3<span *ngIf="signupForm.get('userData.username').errors['nameIsForbidden']">
4	This name is invalid!
5</span>
1this.signupForm = new FormGroup({
2  'email': new FormControl(
3		null, [Validators.required, Validators.email], this.forbiddenEmails)
4																								// ^can be an array,
5																								// this position is for async
6});
7
8forbiddenEmails(control: FormControl): Promise<any> | Observable<any> {
9  const promise = new Promise<any>((resolve, reject) => {
10    setTimeout(() => {
11      if (control.value === '[email protected]') {
12        resolve({'emailIsForbidden': true});
13      } else {
14        resolve(null);
15      }
16    }, 1500);
17  });
18  return promise;
19}
1ngOnInit() {
2	this.signupForm.valueChanges.subscribe(
3		(value) => console.log(value);
4	);
5}
1this.signupForm.setValue({
2  'userData': {
3    'username': 'Max',
4    'email': '[email protected]'
5  },
6  'gender': 'male',
7  'hobbies': []
8});
9// down side -> overwrite all fields whenever we click "Suggest an Username".
1// If we wanna set value TO A SINGLE ONE FIELD?
2this.signupForm.patchValue({
3  'userData': {
4    'username': 'Anna',
5  }
6});
1onSubmit() {
2	this.signupForm.reset();
3										//  ^you can put object to reset specific values
4}