Showing posts with label Reactive forms. Show all posts
Showing posts with label Reactive forms. Show all posts

Friday, March 30, 2018

Added a generateForm() Method to vadacl

Today I published a new version of my vadacl library that includes a new convenience method:  generateForm().  The generateForm() method accepts a data object and returns a FormGroup with FormControl instances generated from the properties and any vadacl-based validation configurations associated with the data object.

So instead of declaring the FormGroup and FormControl instances manually:

export class CruiseShipComponent extends Vadacl implements OnInit {
  cruiseShip: CruiseShip;
  shipForm: FormGroup;

  ngOnInit() {
    this.cruiseShip = new CruiseShip();
    this.shipForm = new FormGroup({
      'name': new FormControl( this.cruiseShip.name, this.applyRules( this.cruiseShip, 'name' ) ),
      'cruiseLine': new FormControl( this.cruiseShip.cruiseLine, this.applyRules( this.cruiseShip, 'cruiseLine' ) ),
      'yearBuilt': new FormControl( this.cruiseShip.yearBuilt, this.applyRules( this.cruiseShip, 'yearBuilt' ) )
    });
  }

...you can simply invoke generateForm() with the data object:

...
ngOnInit() {
  this.cruiseShip = new CruiseShip();
  this.shipForm = this.generateForm( this.cruiseShip );
}

generateForm() also accepts a second arguments:  a "mods" object that allows you to customize the FormGroup returned by the method.  The possible modification properties are:
  • exclude:  an array of data object properties you don't want to create a FormControl for.
  • only:  an array of the only data object properties you want to create FormControls for (overrides the "exclude" property if you mistakenly use both).
  • rename: an object literal for remapping a data object property name (key) to a different FormControl name (value).
  • validations: an object literal of additional validations to apply to particular data object properties.

An example of using the mods argument:
this.shipForm = this.generateForm( this.cruiseShip, {
  exclude: [ 'yearBuilt' ],
  rename: { cruiseLine: 'company' },
  validations: {
     name: { maxLength: { maxLength: 100, message: 'Ship name cannot be longer than 100 characters' } }
  }
});

The Angular application I maintain on GitHub to demonstrate vadacl has been updated with a demo that uses generateForm(). 

Thursday, September 14, 2017

New Version of vadacl Released as an NPM Package

It's been a LONG time since my last blog post.  Work and life simply kept me from doing a lot of personal coding.  But I'm making an effort to get back into the swing of things, and I decided to start that effort by moving forward with my vadacl Angular validation library.

For anyone not familiar with what vadacl is about, here's the current synopsis:

vadacl is a library that extends and enhances reactive form validation in Angular 4.x. It provides:

  • A mechanism for declaring validation logic within domain classes / data objects that can be reused in multiple components.
  • The ability to configure the text of validation failure messages as part of the domain class validation logic or within a global validation message object.
  • Helper methods for triggering validation and displaying validation results.
  • Additional validation methods beyond those provided by Angular 4.x, and the ability to extend the vadacl validation methods with custom methods in your project.
  • The new version of vadacl (currently version 1.0.10) is available as an NPM package at:

    https://www.npmjs.com/package/vadacl

    There is also a separate GitHub repo containing an Angular CLI-powered Angular 4.x app that demonstrates the use of vadacl in different scenarios:

    https://github.com/bcswartz/vadacl-demo

    As part of the transition to an NPM package, vadacl was refactored to allow you to extend or override the validation methods and messages without touching the library files themselves, using files specific to your project.  That will let you update to future versions of vadacl via npm without losing any of your custom code.  The new version was written for Angular 4.x and includes vadacl versions of the newest methods in Angular's Validators class:  min, max, and email.

    The new demonstration application is an updated version of the one still hosted on the old vadacl GitHub repo, and contains not only examples of the new validation methods, but also provides documentation about how to go about extending vadacl and an example of how you can swap out the global messages file with a different one (designed for a different audience or different language) during your build/deployment process.

    Friday, December 30, 2016

    Version 0.2.0 of vadacl Released

    I just released a new version of my vadacl validation library for Angular 2. The new release includes the following updates:

    • To match a recent change to the pattern Validator in Angular 2, vadacl's pattern validation method was updated to accept both string and RegExp pattern arguments.

    • A requiredTrue validation method was added to parallel the recently-added Angular requiredTrue Validator (used primarily for validating that a checkbox has been checked/set to true).

    • The applyCollectionRule() method was added to the Vadacl class. The new method is designed to be used instead of the applyRules() method when applying a single validation method to a FormGroup or FormArray.

    • Added three new validation methods specifically for FormGroup and FormArray validation:

      • totals: validates that the sum of the numeric values of the FormGroup or FormArray controls equals a certain amount.

      • equalValues: validates that all of the values of the FormControls within a FormGroup or FormArray are exactly equal. Useful for performing password confirmation.

      • withinTrueCount: validates that the number of FormControls within a FormGroup or FormArray with a value of Boolean true falls within a given range. Designed primarily to validate how many checkboxes are checked.

    • Added and updated demos to demonstrate the new validation methods.

    • Updated the demo codebase to Angular 2.4.1

    Tuesday, November 29, 2016

    Locale-Based Message Support Added to vadacl

    The latest release of my vadacl validation library for Angular 2 introduces a new feature:  locale-based error message configuration.

    Prior to this release, vadacl would allow you to configure the error message for a particular validation error condition for a particular object property in either the object-based validation settings or in the component responsible for the form controls.  And if you did not declare an error message in either of those places, the validation method would return a method-specific default error message from the Messages object.  So in the following (slightly-contrived) scenario:

    
    // app/vadacl/locale/messages-en.ts
    let Messages = {
      /* DEFAULT LOCALE VALIDATOR ERROR MESSAGES */
      required: 'A value is required',
      ...
    }
    
    
    // app/domain/user-profile.ts
    ...
    export class UserProfile implements Validatable {
      firstName: string = null;
      lastName: string = null;
      username: string = null;
      age: number = null;
      gender: string = null;
    
      validations: { [ index: string ] : PropertyValidations } = {
        firstName: {
           required: { message: 'Your first name is required.' }
        },
        lastName: {
           required: {}
        },
        username: {
           required: { message: 'Username is required.' }
        },
        age: {
           required: {}
        },
        gender: {
           required: {}
        }
      };
      ...
    }
    
    // app/forms/user-profile-form.component.ts
    ...
    export class UserProfileForm extends Vadacl implements OnInit {
      profileForm: FormGroup;
      userProfile: UserProfile;
      ...
    
      ngOnInit() {
        this.userProfile = new UserProfile();
    
        this.profileForm = new FormGroup({
          'firstName': new FormControl(
             this.userProfile.firstName,
             this.applyRules( this.userProfile, 'firstName' )
          ),
    
          'lastName': new FormControl(
             this.userProfile.lastName,
             this.applyRules( this.userProfile, 'lastName', { required: { message: 'Your last name is required.' } )
          ),
    
          'username': new FormControl(
             this.userProfile.username,
             this.applyRules( this.userProfile, 'username', { required: { message: 'Please enter a username.' } )
          ),
    
          'age': new FormControl(
             this.userProfile.age,
             this.applyRules( this.userProfile, 'age' )
          ),
    
          'gender': new FormControl(
             this.userProfile.gender,
             this.applyRules( this.userProfile, 'gender' )
          )
        });
      }
      ...
    }
    
    ...the validation error message for each UserProfile property in the UserProfileForm template that fails the required validation would be:
    • firstName: "Your first name is required."
    • lastName: "Your last name is required." (the message is provided during the FormControl instantiation)
    • username: "Please enter a username." (the component-level message overrides the object-level message)
    • age: "A value is required"
    • gender: "A value is required"

    Now vadacl provides the option to configure error messages for object properties in the Messages object rather than in the validation settings in the object. Refactoring the example above to use the new feature, the code would look like:

    
    // app/vadacl/locale/messages-en.ts
    let Messages = {
      /* DEFAULT LOCALE VALIDATOR ERROR MESSAGES */
      required: 'A value is required',
      ...
    
      /* LOCALE-BASED DOMAIN CLASS MESSAGES */
      UserProfile: {
        firstName: {
          required: 'First name is required.'
        },
        lastName: {
          required: 'Last name is required.'
        },
        username: {
          required: 'Username is required.'
        }
      }
      ...
    }
    
    // app/domain/user-profile.ts
    ...
    export class UserProfile implements Validatable { 
      ...
      validations: { [ index: string ] : PropertyValidations } = {
        firstName: { required: {} },
        lastName: { required: {} },
        username: { required: {} },
        age: { required: {} },
        gender: { required: {} }
      };
      ...
    }
    
    // app/forms/user-profile-form.component.ts
    ...
      'lastName': new FormControl(
        this.userProfile.lastName,
        this.applyRules( this.userProfile, 'lastName' )
      ),
    
      'username': new FormControl(
        this.userProfile.username,
        this.applyRules( this.userProfile, 'username', { required: { message: 'Please enter a username.' } )
      ),
    ...
    
    ...and the error messages would now be:
    • firstName: "First name is required."
    • lastName: "Last name is required."
    • username: "Please enter a username."
    • age: "A value is required"
    • gender: "A value is required"

    The validation message defaults to the message in the Messages object that matches the object / property / validation method combination. If a different message is defined for that property in the object validation settings (which ordinarily you wouldn't do if you wanted to use the locale-based messages in the Messages object) or in the component when configuring the FormControl, that other message would become the message value returned when validation fails.

    Configuring the validation messages in the Messages object allows you to keep all of the object-level messages in one place.  In theory, this should also give developers who need to perform internationalization the option of creating Messages objects for different languages and then altering which Messages file is imported into validation-methods.ts file as a build step prior to compiling the app.

    Friday, November 11, 2016

    vadacl: A Library For Streamlining Form Validation in Angular 2

    The initial version of my TypeScript-based vadacl library for performing form validation in Angular 2 is now available on GitHub.

    vadacl provides the following enhancements to the typical implementation of form validation via the reactive form classes (FormControl, FormGroup, FormArray, and FormBuilder):

    • Instead of configuring all of the validation in the component hosting the form, you can configure certain validations within the data object itself (validation rules that should remain consistent wherever the data object is used in your application), then add to or modify those validation rules in the component to create the final set of validations needed for a given form.

    • The vadacl validation methods add a "message" property to the metadata object returned when the form data fails validation.  This "message" property value is the message meant to be presented to the user, and can be configured and/or overridden at multiple levels:

      • The method level, via a set of default message values
      • The data object
      • The component level
    • The Vadacl class, whether used as a superclass for your component or as a service, provides methods for providing an array of validator methods to the FormControls in your form and for displaying the validation error "message" values in your template, removing the need to add multiple DOM elements with "ngIf" directives to display each kind of validation error or to add code to your component to gather and translate validation failures into error messages.

    vadacl is (currently) a small library contained in a single folder you can just drop into your Angular application. The GitHub repo contains that folder as part of a small Angular 2.1.1 application containing several working demos of vadacl in action.

    Enjoy!

    Tuesday, November 8, 2016

    Learning Angular 2: Implementing My vadacl Validation Library

    Version 0.0.7 of my sandbox GuildRunner Angular 2 application is a refactor of the sandbox Chapter form I created in the previous version.  I refactored the form, which uses Angular's reactive form classes (FormControl, FormGroup, FormArray, and FormBuilder) to use a small validation library I created called vadacl.

    The two main features of vadacl are:

    • It allows developers to set validation rules for the properties of a domain class at both the domain class level and the component level (because some validations are there to ensure the data can be persisted back to the server, and those validations should be set on the domain class so they are consistent throughout the application).

    • It gives developers the ability to set a "message" value that will be part of the metadata object returned from a validator when the data is invalid (which you can see in action in the refactored sandbox Chapter form).

    You can read more about vadacl in my blog post about the library and in the README file in the vadacl GitHub repo.

    Wednesday, October 5, 2016

    Learning Angular 2: Exploring Reactive Form Classes and Validators

    Version 0.0.6 of my sandbox GuildRunner Angular 2 application adds an example of using the reactive form and Validator classes provided by Angular 2.  The example was added to the sandbox collection of components rather than the main application, as I plan on taking what I learned from the exercise and expanding on it when I write the "real" forms.

    In my blog post regarding GuildRunner release 0.0.4, which was my take on handling validation for Angular template-driven forms, I incorrectly stated that the other approach to forms supported by Angular 2 was referred to as "dynamic forms."  That's not the case:  the documentation page I was referring to was about how to dynamically generate form inputs for a collection of model data, which is a scenario where using the reactive form classes makes a lot of sense.  The documentation page that gave me the correct name to the alternative to template-driven forms - reactive forms - was the page on form validation.

    In the reactive form style, you do not bind your form controls to your model data.  Instead, you bind them to the reactive form classes, which are:

    The most basic reactive form - a form with a single input - would be constructed with a single FormGroup containing a single FormControl for the form input.  So a reactive form containing a single text input (say a "name" field with an initial value of "Bob") would be coded in the component like so:

    
    /*
    Import the reactive form classes (your Angular module will also have to import the Angular ReactiveFormsModule)
    */
    import {FormControl, FormGroup } from '@angular/forms';
    ...
    export class MyReactiveFormComponent implements OnInit {
      myForm: FormGroup;
      
      ngOnInit() {
        this.myForm = new FormGroup( {
          'name': new FormControl( 'Bob' )
        } )
      }
    

    ...And the HTML template for the form would be written like so:

    
    <form [formGroup]="myForm">
      <input type="text" formControlName="name">
    </form>
    

    Note how the HTML form is connected to the FormGroup via the formGroup attribute, and how the text input is bound to the FormControl via the formControlName attribute: ngModel is not in play here.

    Form input validation is applied by adding validator functions to the FormControl: a FormControl takes either a single validator function or an array of validator functions as its second constructor argument. Making the name input in our example required with a minimum length of 2 characters is a simple matter of adding the necessary validator functions shipped with Angular 2 within the Validators class:

    
      ngOnInit() {
        this.myForm = new FormGroup( {
          'name': new FormControl( 'Bob', [ Validators.required, Validators.minLength(2) ] )
        } )
      }
    

    A validation check will occur anytime the value of the FormControl changes, whether that change is made via the UI or programmatically (which is an improvement over how the template-driven forms work).

    A FormGroup can contain any number of FormControl objects.  It can also contain additional FormGroups (sub-groups within the main FormGroup) and FormArrays which hold a collection of unnamed, iterable FormControls.  A single validator function can be attached to each FormGroup and FormArray, usually a custom validator function that performs a validation based on multiple form values.

    To try out these features, I created a form for updating certain properties of a Chapter domain class:

    • A text input for updating the chapter name.
    • A select box for selecting the guild the chapter belongs to.
    • A radio button for setting whether or not the chapter was the head chapter for the guild.
    • A series of checkboxes representing the defense measures used at the chapter location, represented in the Chapter domain class an array of defense measure ID values.
    In the component, I created a single method (called in ngOnInit) for instantiating the reactive form classes and for subscribing to the change event emitter:
    
    //sandbox/chapter-reactive-form/chapter-reactive-form.component.ts
    import { Chapter } from "../../domain/chapter";
    import { guilds } from "../../db/guilds";
    import { defenseMeasures } from "../../db/defense-measures";
    
    import {FormControl, FormGroup, FormArray, Validators, FormBuilder} from '@angular/forms';
    ...
    export class ChapterReactiveFormComponent implements OnInit {
    
      chapter: Chapter;
      defenseArray: any = []; //Populated by ngOnInit with an array of defenses
      guildArray: any = []; //Populated by ngOnInit with an array of guilds
      defenseBoxArray: FormArray;
      form: FormGroup;
      ...
      constructor( private formBuilder: FormBuilder ) { }
      ...
      buildForm() {
    
        //Create a custom Validator function for the defenses array
        function hasDefenses( formArray: FormArray) {
          let valid = false;
          for( let c in formArray.controls ) {
            if( formArray.controls[c].value == true ) { valid = true }
          }
          return valid == true ? null : { noDefenses: true }
        }
    
        //Construct and populate the defenses FormArray outside of the FormBuilder so we can populate it dynamically
        this.defenseBoxArray = new FormArray( [], hasDefenses );
        for( let d in this.defenseArray ) {
          this.defenseBoxArray.push( new FormControl(( this.chapter.defenses.indexOf( this.defenseArray[d].id ) > -1 )))
        }
    
        this.form = this.formBuilder.group( {
          'name': [ this.chapter.name, [
            Validators.required,
            Validators.minLength(4),
            Validators.pattern('[a-zA-Z]+')
          ] ],
          'guild': [ this.chapter.guildId, Validators.required ],
          'headChapter': [ this.chapter.headChapter, Validators.required ],
          'defenses': this.defenseBoxArray
        } );
    
        this.form.valueChanges
          .subscribe( data => this.checkFormValidity( data ) );
      }
    
    

    The hasDefenses() function definition is an example of how to create a custom validator function, which should either return null if validation passed or return an object literal that provides some context for why the validation failed. The function is then passed as the 2nd argument in the FormArray constructor.

    The rest of the FormGroup representing the form is created using the FormBuilder, which provides a less verbose way to instantiating the other reactive form classes. The final statement in the method subscribes to the valueChanges event emitted by the form anytime a form value is updated and ties that event to the execution of the checkFormValidity method which I'll touch on shortly.

    The HTML form controls that bind to these reactive form elements looks like this:

    
    <-- sandbox/chapter-reactive-form/chapter-reactive-form.component.html -->
    <form class="form-horizontal well well-sm" *ngIf="chapter" [formGroup]="form">
      ...
      <input id="name" type="text" class="form-control" formControlName="name">
      ...
      <select id="guild" class="form-control" formControlName="guild">
        <option [selected]="form.controls.guild.value == null" value="">-- Select --</option>
        <option *ngFor="let g of guildArray" [selected]="g.id == guild" [value]="g.id">{{g.name}}</option>
      </select>
      ...
      <input type="radio" formControlName="headChapter" name="headChapter" value="true" [checked]="form.controls.headChapter.value === true"> Yes   
      <input type="radio" formControlName="headChapter" name="headChapter" value="false" [checked]="form.controls.headChapter.value === false"> No
      ...
      <ul formArrayName="defenses"> <!-- Must set the formArrayName -->
        <li *ngFor="let def of form.controls.defenses.controls; let i = index">
          <input type="checkbox" formControlName="{{i}}" > {{defenseArray[i].label}}
        </li>
      </ul>
    

    A few things worth pointing out:

    • The "--Select--" option for the guild drop-down was something I added so that text was displayed in the select box when the current value was null. The control would work fine without it.
    • Setting the "checked" attribute on the radio buttons based on the current headChapter form control state was necessary in order to show the initial value.
    • When a form control belongs to either a sub-FormGroup or a FormArray (as in this case), you need to use the formGroupName or formArrayName as an attribute in an HTML element that encloses the HTML elements bound to the FormControls within the FormGroup or FormArray.

    As mentioned earlier, a validator function returns an object literal with context information about the validation problem when validation fails.  Those object literals need to be translated into appropriate error messages to display to the user.  So in a similar fashion to what I did with my template-driven form, I had to provide some translations in my component as well as a collection of arrays to hold the translated error messages:

    
    //sandbox/chapter-reactive-form/chapter-reactive-form.component.ts
      ...
      errMsgs: any = {
        name: [],
        guild: [],
        headChapter: [],
        defenses: []
      };
    
      translations: any = {
        name: {
          required: 'The name is required.',
          minlength: 'The name must be at least 4 characters long.',
          pattern: 'The name can only contain letters.'
        },
        guild: {
          required: 'Please select a guild.'
        },
        headChapter: {
          required: 'Please select either Yes or No.'
        },
        defenses: {
          noDefenses: 'The chapter must implement at least one defensive measure.'
        }
      };
    

    Note how each translation block consists of the name of the form control and an object literal whose properties names match up with the object literal keys returned by the validator functions (including the one returned by my custom hasDefenses() function).

    The checkFormValidity() function (executed when the form emits the event indicating a form value has changed) performs the work of examining the current validation errors generated by the reactive form controls and creating the proper user-appropriate error messages:

    
    /sandbox/chapter-reactive-form/chapter-reactive-form.component.ts
      ...
      checkFormValidity( data?: any ){
        for( let k in this.errMsgs ) {
          this.errMsgs[k] = [];
          if( this.form.controls[k].errors && this.form.controls[k].dirty ) {
            for( let e in this.form.controls[k].errors ) {
              if( this.translations[k][e] ) {
                this.errMsgs[k].push( this.translations[k][e] );
              }
            }
          }
        }
      }
    

    Note that the validation error translation only occurs when the invalid form control is in a "dirty" state:  like template-driven form HTML elements, each form control has status values denoting if the form control is pristine or dirty, valid or invalid, and untouched or touched.  Preventing the users from seeing any validation errors when the control is pristine is desirable when you have a form that may initially be empty:  you don't want to display an error on a required field before the user has entered any data.  However, there is a drawback:  if you do change a form value programmatically, the change will trigger a validation check on the form control but it won't change the control state to dirty.  The workaround for that is to manually mark the field as dirty prior to changing the value, as demonstrated in this method:

    
    changeName() {
      this.form.controls['name'].markAsDirty();
      this.form.controls['name'].setValue( '999' ); //invalid based on the [a-zA-Z]+ pattern validator
    }
    

    Flipping back to the HTML template, the user-appropriate error messages are displayed under the form controls like so:

    
    <div *ngIf="errMsgs.name.length" class="alert alert-danger">
      <ul>
        <li *ngFor="let error of errMsgs.name">
          {{error}}
        </li>
      </ul>
    </div>
    

    Another benefit to using the reactive form classes is that the FormGroup class comes with a reset() method that not only blanks/nulls out the targeted form control values, it also resets the form controls back to a pristine and untouched state.

    The final piece of the puzzle was to write a submit method that would copy the form control values back to the Chapter object (I also coded the submit button to be disabled whenever the FormGroup representing the form was flagged as invalid):

    
      submitForm() {
        this.checkFormValidity()
        if( this.form.valid ) {
          this.chapter.name = this.form.value.name; //value is a key/value map
          this.chapter.guildId = +this.form.value.guild; //need this translated to number, hence +
          this.chapter.headChapter = this.form.value.headChapter === "true";
          this.chapter.defenses = [];
          for( let db in this.defenseBoxArray.controls ) {
            if( this.defenseBoxArray.controls[ db ].value == true ) {
              this.chapter.defenses.push( this.defenseArray[ db ].id )
            }
          }
        }
      }
    

    I then added interpolations to the template that would display the state and raw error values of the form controls as well as the current Chapter model values so I could watch everything in action when using the form:

    Some final notes:

    • You may notice in the animated GIF of the form that the reset action did not clear the radio buttons. That's due to the fact that I'm still using RC5: that bug was fixed in the official release of Angular 2.

    • Although you can supply a validator function as an argument when instantiating a new FormGroup, for some reason the FormBuilder syntax for creating a FormGroup does not allow you to provide a validator. So if you need to add validation to a FormGroup object, instantiate the object ahead of time and then reference it in the FormBuilder construction (just like I did with my FormArray).

    Monday, September 5, 2016

    Learning Angular 2: Experiment in Validating a Template-Driven Forms

    Version 0.0.4 of my GuildRunner sandbox Angular 2 application is now available.

    In an earlier post, I shared my thoughts after working through the examples on the official documentation page for template-driven forms.  I came away underwhelmed with the features designed to help with validating the form input:

    • The classes Angular adds to form controls to indicate the control state are based on interactions with the DOM and don't reflect the state of the model data attached to the control (for example, once a form control is marked as "dirty", changing the control value back to its original value does not re-mark it as "clean").

    • Some basic form control attributes (like "required") could be used to set validation constraints that Angular would use to toggle the valid/invalid class on the control, but not the validation attributes introduced in HTML5, and there was no documentation about what worked and what didn't.

    • Even when Angular could toggle the valid/invalid class correctly, that only indicated that the form control should be considered invalid, not why it was invalid.

    (The change log for the recently released RC6 version of Angular 2 hints that some of these issues may have been addressed.)

    So when I decided that the next feature of GuildRunner would be a detail component for adding or editing a guild, I knew figuring out my strategy for validating the form would be a big part of the work involved.  What I came up for this release is a rough, first draft of an approach where the form takes its validation cues from validation logic and state data stored in the component.  There is a lot of room for improvement should I decide that this approach is viable and a reasonable alternative to the other method of generating and managing forms in Angular 2 (dynamic forms).

    I started by making a few changes to my guild data and my Guild domain class.  I added two new properties:  "email" and "incorporationYear" (the year the guild was incorporated).  I also refactored the Guild constructor to make the object literal argument and all of properties optional:
    
    //domain/guild.ts
    ...
    constructor( guildData?: any  ) {
        if( guildData ) {
          this.id = guildData.id ? guildData.id : null ;
          this.name = guildData.name ? guildData.name : null ;
          this.email = guildData.email ? guildData.email : null;
          this.incorporationYear = guildData.incorporationYear ? guildData.incorporationYear : null;
    ...
    
    I then created the initial GuildsDetailComponent and defined two routes to reach it from the GuildsMasterComponent, one route for editing and one for adding:
    
    //app.routing.ts
    { path: 'guilds/:id', component: GuildsDetailComponent}, //Edit route
    { path: 'guild', component: GuildsDetailComponent },  //Add route
    
    ...I thought about just having the one route with the parameter value, where a parameter value of 0 would be used to trigger the add behavior, but I wanted to avoid that if I could. Having a route that didn't provide a parameter meant I had to handle that in the ngOnInit method of GuildsDetailComponent:
    
    //guilds-detail.component.ts
    ...
    export class GuildsDetailComponent implements OnInit {
    
      guild: Guild;
      ...
      ngOnInit() {
          this.route.params.forEach( (params: Params ) => {
            let id = params['id'] ? +params['id'] : null ;  //converts param string to number
            if( id ) {
              //Ask the GuildService for a Guild object instantiated with data for that record
            } else {
              this.guild = new Guild();  //Instantiate a "blank slate" Guild object
            }
    ...
    
    I created a bare-bone GuildService method to provide GuildsDetailComponent with a populated Guild object for the selected guild, then started working on the form and the form logic. I tried a few different approaches before settling on the format in the current release.

    The component HTML starts off with a header block:
    
    <!-- guilds-detail.component.html -->
    <div class="row">
      <div class="col-md-12">
        <h6 *ngIf="!guild && !serviceErrors">Loading...</h6>
        <h3 *ngIf="guild">{{(guild.id ? 'Edit' : 'Add' )}} {{(guild.name ? guild.name : 'Guild')}}</h3>
      </div>
    </div>
    ...
    
    The <h3> interpolation logic makes sure we end up with an appropriate title based on the situation, and the ngIf directives make sure we display the appropriate content under the appropriate conditions.

    The form is styled with Bootstrap and contains text inputs for the name, incorporationYear, and email address for the guild.  Except for the attributes that are specific to the guild data bound to each control, the HTML is essentially the same:
    
    <!-- guilds-detail.component.html -->
    ...
    <div class="form-group">
        <label for="name" class="col-md-2 control-label">Name:</label>
        <div class="col-md-6">
          <input id="name" type="text" class="form-control" [(ngModel)]="guild.name" (change)="checkValidity( 'name' )" (keyup)="checkFix( 'name' )" name="name" #name="ngModel" />
          <div *ngIf="status.name.errors.length" class="alert alert-danger">
            <ul>
              <li *ngFor="let error of status.name.errors">
                {{error}}
              </li>
            </ul>
          </div>
        </div>
    </div>
    
    So [(ngModel)] binds this control to the name property of the Guild object of the component, and checkValidity() and checkFix() are executed in response to the change and keyup events emitted by the control. Any errors regarding this input are managed via an array of errors attached to the "name" property of a simple "status" object literal defined in the component.

    All of the validation work occurs within the checkValidity() method of the component:
    
    //guilds-detail.component.ts
    ...
    checkValidity( propertyName: string ) {
    
          let yearRegEx = /[1-2]\d{3}$/;
          let emailRegEx = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/;
    
          switch( propertyName ) {
            case 'name':
              this.status.name.errors = [];
              if( !this.guild[ propertyName ] ) {
                this.status.name.errors.push( 'The name field is required.' );
                break;
              }
              if( this.guild[ propertyName ].length < 5 ) {
                this.status.name.errors.push( 'The guild name is too short.' );
              }
              break;
            case 'incorporationYear':
              this.status.incorporationYear.errors = [];
              if( !this.guild[ propertyName ] ) {
                this.status.incorporationYear.errors.push( 'The year of incorporation is required.' );
                break;
              }
              if( isNaN( +this.guild[ propertyName ] ) ) {
                this.status.incorporationYear.errors.push( 'The incorporation year must be a number.' );
              } else {
                if( !yearRegEx.test( this.guild[ propertyName ] ) ) {
                  this.status.incorporationYear.errors.push( 'The incorporation year must a valid 4-digit year (1xxx or 2xxx).' );
                }
              }
              break;
            case 'email':
              this.status.email.errors = [];
              if( !emailRegEx.test( this.guild[ propertyName ] ) ) {
                this.status.email.errors.push( 'Please enter a valid email address.' );
              }
              break;
          }
    
    }
    
    So every time one of the text inputs emits a change event, checkValidity() clears the existing validation error array for that control/guild property and executes the relevant validation logic, re-populating that error array with any validation issues that still exist.  And based on the component HTML, any such errors are displayed in an alert area beneath the text input.

    The change event for the text inputs doesn't fire until the form input loses focus, which is fine when the user is first inputing data into the form control because you don't want any minimum length validation rules (like the one for the guild name) applied before they finish typing.  But that does mean that if a validation error is displayed, it won't disappear until after the user has modified the input and then exited the input again.  That's not a terrible experience, but I wanted to ensure the user doesn't leave the input again and still end up with an invalid value.  That's why the keyup event executes the checkFix() method in the component.
    
    //guilds-detail.component.ts
    ...
    checkFix( propertyName: string ) {
        if( this.status[ propertyName ].errors.length > 0 ) {
          this.checkValidity( propertyName );
        }
      }
    
    The checkFix() method checks the validity with every keystroke until the validation issues have all been resolved. And that point, one would hope the user wouldn't enter more text in the input that would make the content and the control invalid again.

    The rest of the form and form logic is pretty straightforward.  The form concludes with three buttons:
    • The "Cancel" button, which simply navigates back to the table list of guilds.
    • The "Save" button, which is disabled as long as there are any validation errors and when clicked executes checkValidity() for every property contained in the "status" object literal of the component (so for all of the form items).
    • The "Clear Form" button, which sets the name, incorporationYear, and email properties of the guild object to null and clears all of the previous validation errors, providing a clean slate for data entry.
    The end result is a form that behaves like this:

    In terms of the behavior, I'm pleased with the result, but the implementation could be better.  Most if not all of the validation configuration should belong in the Guild object rather than the component, the repetition of the template code for each form control suggests I could probably create a custom component to encapsulate the shared behavior, and I need to try applying the same technique to more complex forms to see if the implementation holds up under different scenarios.

    Other notes regarding this release:
    • I created an HttpResponse domain class to use to pass data back from the service methods to the component methods that called them.  I did that in order to create consistency in how the response data from HTTP calls was packaged and presented to the calling methods, whether the HTTP call returned successfully or returned with a HTTP error code.

    • I wanted to account for the scenario where a user bookmarked the application URL that would pull up a particular Guild in the GuildsDetailComponent, but that guild no longer existed in the data.  So if the GuildService getGuild() method is executed in that scenario, the 404 error response will populate the HttpResponse object returned to the GuildsDetailComponent with an error message that no guild matching that id number exists, and that will end up being displayed instead of the form.