|  | a |  | ### Form validation example (http://blog.ng-book.com/the-ultimate-guide-to-forms-in-angular-2/) | 
|  | \ No newline at end of file |  | ```javascript | 
|  |  |  | ... | 
|  |  |  | import { ValidationService } from '../../services'; | 
|  |  |  | import { ControlArray, ControlGroup } from '@angular/common'; | 
|  |  |  | import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms'; | 
|  |  |  |  | 
|  |  |  | @Component... | 
|  |  |  | export class ContactForm implements OnInit { | 
|  |  |  | private contactForm: FormGroup; | 
|  |  |  |  | 
|  |  |  | public constructor (private formBuilder: FormBuilder) {} | 
|  |  |  |  | 
|  |  |  | public ngOnInit () { | 
|  |  |  | this.buildForm(); | 
|  |  |  | } | 
|  |  |  |  | 
|  |  |  | private buildForm () { | 
|  |  |  | let nameControl = ['', Validators.required];  // can also use other initial value instead of empty string | 
|  |  |  | let emailControl = ['', ValidationService.emailValidator]; | 
|  |  |  | let subjectControl = ['', Validators.required]; | 
|  |  |  | let bodyControl = ['', Validators.required]; | 
|  |  |  |  | 
|  |  |  | this.contactForm = this.formBuilder.group({ | 
|  |  |  | full_name: nameControl, | 
|  |  |  | email: emailControl, | 
|  |  |  | subject: subjectControl, | 
|  |  |  | body: bodyControl, | 
|  |  |  | }); | 
|  |  |  | } | 
|  |  |  |  | 
|  |  |  | private submitForm (form: any): void { | 
|  |  |  | if (form.valid) { | 
|  |  |  | let payload = form.value; | 
|  |  |  | this.Contact.create(payload).subscribe((res: any) => { | 
|  |  |  | this.buildForm(); | 
|  |  |  | this.handleSuccess(); | 
|  |  |  | }, (err: any) => { | 
|  |  |  | console.log(err); | 
|  |  |  | }); | 
|  |  |  | } | 
|  |  |  | else {console.log('FORM: INVALID');} | 
|  |  |  | } | 
|  |  |  |  | 
|  |  |  | ... | 
|  |  |  |  | 
|  |  |  | } | 
|  |  |  | ``` | 
|  |  |  |  | 
|  |  |  |  | 
|  |  |  | ## Custom app-global error handling | 
|  |  |  | ```typescript | 
|  |  |  | import { ErrorHandler } from '@angular/core'; | 
|  |  |  |  | 
|  |  |  | class CustomErrorHandler implements ErrorHandler { | 
|  |  |  | public handleError (error: any) { | 
|  |  |  | // originalError is probably the message you want in a toast, etc | 
|  |  |  | console.log(error.originalError); | 
|  |  |  | // still send error to console, if desired | 
|  |  |  | console.error(error._nativeError); | 
|  |  |  | } | 
|  |  |  | } | 
|  |  |  | ``` | 
|  |  |  | ``` | 
|  |  |  | import { NgModule, ErrorHandler } from '@angular/core'; | 
|  |  |  | import { CustomErrorHandler } from './path/to/custom/handler'; | 
|  |  |  | ... | 
|  |  |  | @NgModule({ | 
|  |  |  | ... | 
|  |  |  | providers: [ | 
|  |  |  | {provide: ErrorHandler, useClass: CustomErrorHandler}, | 
|  |  |  | ], | 
|  |  |  | }) | 
|  |  |  | ``` | 
|  |  |  |  | 
|  |  |  | ## ngFor filtering (ngChange on input which updates array) | 
|  |  |  |  | 
|  |  |  | ## Add the following for errors to appear and disappear automatically | 
|  |  |  |  | 
|  |  |  | ```scss | 
|  |  |  | /* FORMS, INPUTS | 
|  |  |  | ----------------------------------------*/ | 
|  |  |  | input, textarea { | 
|  |  |  | display: inline-block; | 
|  |  |  | padding: 16px 28px; | 
|  |  |  | border: none; | 
|  |  |  | outline: none; | 
|  |  |  | box-shadow: none; | 
|  |  |  | border-radius: 60px; | 
|  |  |  | color: $gray; | 
|  |  |  | @include font-range(14, 18); | 
|  |  |  | @include sans; | 
|  |  |  | @include placeholder { | 
|  |  |  | color: $grayLt; | 
|  |  |  | @include sans; | 
|  |  |  | @include font-range(14, 18); | 
|  |  |  | } | 
|  |  |  | } | 
|  |  |  | input.ng-invalid.ng-dirty, textarea.ng-invalid.ng-dirty { | 
|  |  |  | border-left: solid red 3px !important; | 
|  |  |  | } | 
|  |  |  | input.ng-valid.ng-dirty, textarea.ng-valid.ng-dirty { | 
|  |  |  | border-left: solid #42A948 3px !important; | 
|  |  |  | } | 
|  |  |  | ``` | 
|  |  |  | ## Example of errors in form HTML | 
|  |  |  | ```html | 
|  |  |  | <div class="careerDetail"> | 
|  |  |  | <div class="careerDetail__content"> | 
|  |  |  | <section class="careerOutline"> | 
|  |  |  | <h2 class="careerOutline__title">{{ (career | async)?.title }}</h2> | 
|  |  |  | <div class="careerOutline__location">{{ (career | async)?.location }}</div> | 
|  |  |  | <div [innerHtml]="(career | async)?.description"></div> | 
|  |  |  | </section> | 
|  |  |  | <section class="apply"> | 
|  |  |  | <form [formGroup]="applyForm" (submit)="onSubmit(applyForm)"> | 
|  |  |  | <div class="apply__header"> | 
|  |  |  | APPLY FOR THIS POSITION | 
|  |  |  | </div> | 
|  |  |  | <div class="apply__form"> | 
|  |  |  | <input required [formControl]="applyForm.controls['full_name']" class="apply__input" type="text" placeholder="Full Name *"> | 
|  |  |  | <input required [formControl]="applyForm.controls['city']" class="apply__input" type="text" placeholder="City*"> | 
|  |  |  | <input required [formControl]="applyForm.controls['email']" class="apply__input" type="email" placeholder="Email*"> | 
|  |  |  | <input required [formControl]="applyForm.controls['phone_number']" class="apply__input" type="text" placeholder="Phone*"> | 
|  |  |  | <div class="formError" *ngIf="applyForm.controls['phone_number'].touched && applyForm.controls['phone_number'].hasError('invalidInternationalPhoneNumber') && | 
|  |  |  | applyForm.controls['phone_number'].value.length > 0"> | 
|  |  |  | Number needs to be in International format. ex) +18881231234 | 
|  |  |  | </div> | 
|  |  |  | <textarea required [formControl]="applyForm.controls['message']" class="apply__textarea" placeholder="Message (required)" | 
|  |  |  | rows="10"></textarea> | 
|  |  |  | <input required accept='.doc,.pdf,.txt' id="fileInput" class="apply__fileInput" type="file"> | 
|  |  |  | <div class="formError" *ngIf="fileError"> | 
|  |  |  | {{fileError}} | 
|  |  |  | </div> | 
|  |  |  | <button  [disabled]="!applyForm.valid || fileError" type="submit" class="button"> Apply </button> | 
|  |  |  | </div> | 
|  |  |  | </form> | 
|  |  |  | </section> | 
|  |  |  | </div> | 
|  |  |  | </div> | 
|  |  |  | ``` | 
|  |  |  | ## Component for form errors | 
|  |  |  | ``` javascript | 
|  |  |  | import { Component, OnInit } from '@angular/core'; | 
|  |  |  | import { Router, ActivatedRoute } from '@angular/router'; | 
|  |  |  | import { ValidationService, APIConfigService, HttpService, CareerModel } from '../../shared'; | 
|  |  |  | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | 
|  |  |  | @Component({ | 
|  |  |  | selector: 'app-career', | 
|  |  |  | templateUrl: './career.component.html', | 
|  |  |  | styleUrls: ['./career.component.scss'] | 
|  |  |  | }) | 
|  |  |  | export class CareerComponent implements OnInit { | 
|  |  |  | private user: {} = undefined; | 
|  |  |  | private career: Promise<{}>; | 
|  |  |  | private careerId: number; | 
|  |  |  | private applyForm: FormGroup; | 
|  |  |  | private success: boolean = false; | 
|  |  |  | private apiUrl: any = this.apiConfig.apiUrl; | 
|  |  |  | private file: any; | 
|  |  |  | private fileError: string = ''; | 
|  |  |  | public constructor( | 
|  |  |  | private validation: ValidationService, | 
|  |  |  | private apiConfig: APIConfigService, | 
|  |  |  | private Career: CareerModel, | 
|  |  |  | private route: ActivatedRoute, | 
|  |  |  | private router: Router, | 
|  |  |  | private formBuilder: FormBuilder, | 
|  |  |  | private http: HttpService | 
|  |  |  | ) { | 
|  |  |  | } | 
|  |  |  | public ngOnInit() { | 
|  |  |  | this.buildForm(); | 
|  |  |  | this.route.params.subscribe(params => { | 
|  |  |  | this.career = this.Career.read(params['id']) | 
|  |  |  | .then(career => { | 
|  |  |  | this.careerId = career.id; | 
|  |  |  | return career; | 
|  |  |  | }); | 
|  |  |  | }); | 
|  |  |  | } | 
|  |  |  | ngAfterViewInit() { | 
|  |  |  | // Component views are initialized | 
|  |  |  | this.file = document.getElementById('fileInput'); | 
|  |  |  | this.file.onchange = () => { | 
|  |  |  | let ext = this.file.files[0].name.match(/\.([^\.]+)$/)[1]; | 
|  |  |  | if (ext !== ('pdf' || 'doc' || 'txt')) { | 
|  |  |  | this.fileError = 'accepted formats .pdf, .doc, .txt'; | 
|  |  |  | } | 
|  |  |  | else { this.fileError = '' } | 
|  |  |  | } | 
|  |  |  | } | 
|  |  |  | private buildForm() { | 
|  |  |  | let nameControl = ['', Validators.required]; | 
|  |  |  | let emailControl = ['', Validators.compose([Validators.required, this.validation.emailValidator])]; | 
|  |  |  | let cityControl = ['', Validators.required]; | 
|  |  |  | let phoneControl = ['', Validators.compose([Validators.required, this.validation.phoneInternationalValidator])]; | 
|  |  |  | let bodyControl = ['', Validators.required]; | 
|  |  |  | this.applyForm = this.formBuilder.group({ | 
|  |  |  | full_name: nameControl, | 
|  |  |  | email: emailControl, | 
|  |  |  | city: cityControl, | 
|  |  |  | phone_number: phoneControl, | 
|  |  |  | message: bodyControl, | 
|  |  |  | }); | 
|  |  |  | } | 
|  |  |  | private onSubmit(form: any): void { | 
|  |  |  | if (form.valid) { | 
|  |  |  | let payload = form.value; | 
|  |  |  | let url = this.apiUrl + '/jobs/' + this.careerId + '/apply/'; | 
|  |  |  | let file: any = document.getElementById('fileInput'); | 
|  |  |  | file = file.files[0]; | 
|  |  |  | let ext = file.name.match(/\.([^\.]+)$/)[1]; | 
|  |  |  | if (ext === ('pdf' || 'doc' || 'txt')) { | 
|  |  |  | // file = file.files[0]; | 
|  |  |  | let data = new FormData(); | 
|  |  |  | Object.keys(payload).map((key) => { | 
|  |  |  | data.append(key, payload[key]); | 
|  |  |  | }); | 
|  |  |  | data.append('resume', file, file.name); | 
|  |  |  | this.http.clearToken('Content-Type'); | 
|  |  |  | this.http.post(url, data).subscribe((r: any) => { | 
|  |  |  | this.buildForm(); | 
|  |  |  | this.success = true; | 
|  |  |  | setTimeout(() => { this.success = false; }, 3000); | 
|  |  |  | }, (err: any) => { | 
|  |  |  | console.log(err); | 
|  |  |  | }); | 
|  |  |  | } | 
|  |  |  | else { | 
|  |  |  | alert('File is not a .doc,.txt, or .pdf format'); | 
|  |  |  | } | 
|  |  |  | } | 
|  |  |  | else { console.log('FORM: INVALID'); } | 
|  |  |  | } | 
|  |  |  | } | 
|  |  |  |  | 
|  |  |  | ``` | 
|  |  |  | \ No newline at end of file |