Reactive Forms

Custom Validators

In this exercise you will create a custom Validator which validates the entered city names against a hardcoded list of valid cities, but this time without using a Directive, but a function instead.

  1. Implement all new reactive Validators inside the folder shared/validators.

  2. Create a new file city-validator.ts and implement a Validation function validateCity() which uses an AbstractControl as input parameter and shall validate the provided city name by checking a list of valid cities.

    Show code

    import {AbstractControl, ValidationErrors} from '@angular/forms';
    
    export function validateCity(c: AbstractControl): ValidationErrors | null {
        const validCities: string[] = ['Vienna', 'Cologne', 'Bern'];
        if (c.value && validCities.indexOf(c.value) === -1) {
            return {
                city: {
                    actualValue: c.value,
                    validCities: validCities
                }
            };
        }
        return null;
    }
    

  3. Open the file flight-edit.component.ts and register the new Validation function for the from FormControl.

    Show code

    […]
    import {validateCity} from '[…]';
    
    @Component({
        […]
    })
    export class FlightEditComponent implements OnInit {
        […]
        getInitialEditForm(): FormGroup {
            return this.fb.group({
                […]
                from: [
                    null,
                    [
                        […],
                        validateCity
                    ]
                ],
                […]
            });
        }
    }
    

  4. Open the file flight-edit.component.html and make sure whether and error for city was found. In case of an error show are message in your Component.

    Show code

    […]
    <div
        class="alert alert-danger"
        *ngIf="editForm.controls['from'].hasError('city')">
        ...city...
    </div>
    […]
    

  5. Test your application.

Parameterized Validators

Extend the previously implemented Validator, so that it becomes parameterizable. A list of valid city names shall be provided as input parameter.

  1. Open the file city-validator.ts and extend the function validateCity(). It shall use string[] as input parameter and return a Validation function for later use.

    Show code

    import { […], ValidatorFn } from '@angular/forms';
    […]
    export function validateCity (validCities: string[]): ValidatorFn {
        return (c: AbstractControl) => {
            if (c.value && validCities.indexOf(c.value) === -1) {
                return {
                    city: {
                        actualValue: c.value,
                        validCities: validCities
                    }
                };
            }
            return null;
        };
    }
    

  2. Open the file flight-edit.component.ts and change the way the function is used here. In the previous exercise we added a referance to the valdiateCity function so that the Angular framework can use it for starting a validation process later. Now we need to call the outer function by setting the list of valid cities parameter and then the Angular framework can - again - access a function (the inner function that gets returned) to start a FormControl validation whenever it is necessary.

    Show code

    […]
    return this.fb.group({
        […]
        from: [
            null, [
                […],
                validateCity(['Vienna', 'Berlin', 'Gleisdorf'])
            ]
        ],
        […]
    });
    […]
    

  3. Test your application.

Multi-Field-Validators

Implement a Multi-Field-Validator. It shall check whether from and to have the same name. Implement it as exported function so that it can be used in Reactive Forms.

You can assign the reference to the Validator function directly to editForm.validator property.

ngOnInit(): void {
    […]
    this.editForm.validator = validateRoundTrip;
    […]
}
Show code

import { AbstractControl, FormGroup, ValidationErrors } from '@angular/forms';

// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
export function validateRoundTrip(control: AbstractControl): ValidationErrors | null {
    const group = control as FormGroup;

    const from = group.controls.from;
    const to = group.controls.to;

    if (!from || !to) {
        return null;
    }

    if (from.value === to.value) {
        return { roundTrip: true };
    }

    return null;
}

Bonus: Load a Flight *

Load a Flight with an ID of your choice represented as constant value and write it to the form by using the method editForm.patchValue().

In case you implemented Routing already you could receive the ID from the Routing Params and use it to load a specific Flight.

Bonus: Save a Flight *

Implement a save() method and a button in the Template to call this method. The FlightService shall be used to send the updated Flight to your API. You can access the current form value by using editForm.value.

  • Use HttpClient's post() method and the URL http://www.angular.at/api/flight to create or update a Flight.
  • Use a Flight object as second argument of the post() method.
  • To create a new Flight use the Flight's id property set to 0 respectively use a specific id to update one.