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).

Saturday, September 24, 2016

Learning Angular 2: Populating Properties With the Constructor And Using Promise.all

Version 0.0.5 of my GuildRunner sandbox Angular 2 application was focused on updating the model object graph of the application (mainly to provide more opportunities for exploring forms), which included the following changes:

  • Removed the Address domain class and replaced it with a Location object containing traditional, basic address properties. 
  • Created a Person class containing properties such as first name and last name as well as a "residence" property that is an instance of Location.
  • Added the concept of "Chapter", where each Guild has a number of geographically-based Chapters.  Each Chapter domain class is associated with a Guild and has a "location" property that is an instance of ChapterLocation, another new domain class that extends Location and contains properties like "floors" and "entryPoints".
  • Removed the Member domain class and replaced it with ChapterMember, which extends the Person class.
  • Simplified the Guild domain class.
  • Created new master list view for the Chapters and ChapterMembers and added them to the navigation bar.
During the process of refactoring the domain classes, I refined my approach to setting the domain class properties via the constructor.  Previously, I simply declared my properties (with a data type where appropriate) and used ternary operations to set the individual property values like so:

// domain/guild.ts
export class Guild {
  id: number;
  name: string;
  email: string;
  incorporationYear: number;

  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;
    }
  }

It worked, but it would get tedious and hard to read with larger property sets. And having default values set by conditional logic isn't very "default-like" behavior. Compare that to the constructor method I wrote for the new Chapter domain class:


// domain/chapter.ts
import { ChapterLocation } from './chapter-location';

export class Chapter {
  id: number = null;
  guildId: number = null;
  name: string = null;
  location: ChapterLocation = new ChapterLocation();
  headChapter: boolean = false;
  founded: Date = null;
  defenses: Number[] = [];

  constructor( chapterData?: any ) {
    if( chapterData ) {
      let props = Object.keys( this );
      for( let p in props ) {
        if( chapterData[ props[p] ] ) {
          if( props[p] == 'location' ) {
            this.location = new ChapterLocation( chapterData.location )
          } else {
            this[ props[p] ] = chapterData[ props[p] ];
          }
        }
      }
    }
  }

}

It's worth noting that the technique of looping through the properties via Object.keys() only works if we have default values set for each property: properties without values are considered undefined and aren't retrieved by Object.keys().

I also realized that my new master lists of guild chapters and chapter members would look more realistic if they included related data, so I could for example denote the guild and chapter each member belonged to.

Under real-world conditions, a REST request for chapter members would probably include the related guild and chapter data in the returned data. But simulating that kind of all-inclusive data set with the in-memory web API is a bit problematic, because any changes I made to the Guild or Chapter data via the API wouldn't be reflected in the ChapterMember data set. So to avoid that problem, I needed to retrieve the full list of guilds and chapters along with the full list of members.

In Angular 1x, when you needed to collect data returned by multiple promises before proceeding, you could use $q.all() to combine all of the promise results into an array that would become available only after all of the promises returned successfully.  I was able to do the same thing with Promise.all():


// members-master/members-master-component.ts
export class MembersMasterComponent implements OnInit {

  members: ChapterMember[] = [];
  chapters: any = {};
  guilds: any = {};

  constructor(
    private memberService: MemberService,
    private chapterService: ChapterService,
    private guildService: GuildService
  ) { }

  ngOnInit() {

    Promise.all( [ //the array of service calls that return Promises
      this.memberService.getMembers(),
      this.chapterService.getChapters(),
      this.guildService.getGuilds()
    ]).then( (results:Promise[]) => {
      results[0]['data'].forEach( memberData => {
        this.members.push( new ChapterMember( memberData ) )
      });
      results[1]['data'].forEach( chapterData => {
        this.chapters[ chapterData.id ] = chapterData
      });
      results[2]['data'].forEach( guildData => {
        this.guilds[ guildData.id ] = guildData
      });
    });

  }

}

Each service method call returns an instance of my HttpResponse class where the "data" property is populated with the array of member, chapter, and guild data returned by the in-memory web API, and the code loops over each array. Note that the code doesn't access the "data" property via dot-notation: when I tried using dot-notation ("results[0].data") I got a compiler error stating that "data" was not a property of the object. Not sure why: I probably have it coded in such a fashion that TypeScript doesn't recognize the results item as an HttpResponse despite the data typing.

Note that only the member data gets translated into instantiated domain class objects (ChapterMember objects): for the chapters and the guilds, I simply need to capture them such that they can be referenced in the component view:


<-- members-master/members-master.component.html -->
<h3>Chapter Members</h3>
    <table class="table table-bordered table-striped">
      <thead>
      <tr>
        <th>ID</th>
        <th>First Name</th>
        <th>Last Name</th>
        <th>Guild</th>
        <th>Chapter</th>
        <th>Active?</th>
      </tr>
      </thead>
      <tbody>
      <tr *ngFor="let member of members">
        <td>{{member.id}}</td>
        <td>{{member.firstName}}</td>
        <td>{{member.lastName}}</td>
        <td>{{guilds[chapters[member.chapterId].guildId].name}}</td>
        <td>{{chapters[member.chapterId].name}}</td>
        <td>{{member.isActive ? 'Yes' : 'No'}}</td>
      </tr>
      </tbody>
    </table>

Again, this is not the ideal way of gathering related data for a master display of records, but in this case it gets the job done.

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.

 

Tuesday, August 23, 2016

Learning Angular 2: Upgrading to Angular 2 RC5

Version 0.0.3 of my GuildRunner sandbox Angular 2 application is now available.  All of the differences between this version and the previous version (minus the updates to the version number and the README file) are changes made to upgrade the application to use Angular 2 RC5 (release candidate 5).

While there were some changes to the router/routing syntax, the biggest change that comes with RC5 is the introduction of Angular modules and the @ngModule decorator.  There is a long documentation page about Angular modules in the official developer guide, but essentially Angular modules allow you to bundle sets of shared injectable dependencies into a single file that provides those dependencies "downstream".

For example, prior to the upgrade my MainNavigationComponent received the directives needed for routing (like RouterLink) via the "directives" metadata property (which also meant it had to be imported):


//main-navigation.component.ts (previous version)
import { ROUTER_DIRECTIVES } from '@angular/router';
...
@Component({
  ...
  directives: [ ROUTER_DIRECTIVES ]
})

As another example, the GuildsMasterComponent received the GuildService for its constructor method via the "providers" metadata property:


//guilds-master.component.ts (previous version)
import { GuildService } from '../guild.service';
...
@Component({
  ...
  providers: [ GuildService ]
})

Now both of those dependencies are declared in the new application-level Angular module - app.module.ts:


//app.module.ts
...
import { routing } from './app.routing';
import { GuildService } from './guild.service';
...
import { SandboxModule } from './sandbox/sandbox.module';
...
@NgModule({
  imports:      [
    BrowserModule,
    HttpModule,
    routing,  //Provides the routing directives as well as the route definitions
    SandboxModule
  ],

  declarations: [
    AppComponent,
    VersionComponent,
    MainNavigationComponent,
    HomeComponent,
    GuildsMasterComponent
  ],

  providers: [
    { provide: XHRBackend, useClass: InMemoryBackendService }, // in-mem server
    { provide: SEED_DATA,  useClass: InMemoryDataService },     // in-mem server data
    VersionService,
    GuildService //Provides the GuildService downstream
  ],

  bootstrap:    [
    AppComponent
  ],
})

Because the MainNavigationComponent and GuildsMasterComponent are included in the module via the "declarations" block, they are part of the feature bundle of this module, and so they have access, via dependency injection, to the routing and GuildService dependencies without the need for the "directives" or "providers" metadata properties of the @Component.

Note the four properties in this @ngModule decorator. The "declarations" property is where you list all of the components, directives, and custom pipes used in your module: anything your templates need in order to operate (example: the inclusion of the MainNavigationComponent in the declarations allows the AppComponent template to understand how to render the "<app-main-navigation>" tag in the AppComponent HTML). The "providers" property is where you list your module services (things that would be injected into your component as a constructor argument).

The "imports" property is where you list other modules that provide functionality (services, directives, etc.) to your feature module. Some of the modules may be Angular library modules, such as the required BrowserModule or the HttpModule needed for performing HTTP operations. But it can also include other modules in your application. Note the inclusion of the "SandboxModule" in this example:


//app.module.ts
import { SandboxModule } from './sandbox/sandbox.module';
...
@NgModule({
  imports:      [
    ...
    SandboxModule
  ],

That is a separate Angular module file dedicated to the "sandbox" feature area of my application:


//sandbox/sandbox.module.ts
import { NgModule }       from '@angular/core';
import { sandboxRouting } from './sandbox.routing'
import { GuildListComponent } from "./guild-list/guild-list.component";
import { SandboxService } from './sandbox.service';

@NgModule({
  imports: [
    sandboxRouting
  ],

  declarations: [
    GuildListComponent
  ],

  providers: [
    SandboxService
  ]
})

export class SandboxModule {}

This module encompasses the components, services, and the routing related to the sandbox feature area of the application, and it's integrated with the rest of the application simply by the fact that it's declared in the "imports" property of the main application Angular module. Note that it doesn't contain the fourth property seen in app.module.ts: the "bootstrap" property is mainly for declaring the top-level component of a given module, something the sandbox feature area doesn't have.

So the introduction of Angular modules adds a new organizational construct to Angular 2 and cuts down on typing since there's no need to add "directives" and "providers" properties to your components in order to perform dependency injection.

However, there is one small caveat, best explained by example. Even though my app.module.ts file declares the GuildService in its array of providers, and I no longer need to use the "providers" metadata property on my GuildsMasterComponent, I still need to import the GuildService:


//guilds-master.component.ts (new version)
import { GuildService } from '../guild.service';
...
export class GuildsMasterComponent implements OnInit {

  guilds: Guild[] = [];

  constructor( private guildService: GuildService ) { }

This puzzled me, and in perusing some of the updated documentation and tutorial examples I couldn't find an explanation for why that import was still necessary if the GuildsMasterComponent was getting its instance of the GuildService from the application Angular module.

But then I looked at the JavaScript being generated from the guilds-master.component.ts file. Here are the relevant lines from that JavaScript file, with the significant lines followed by comments:


//guilds-master.component.js
...
var guild_service_1 = require('../guild.service');  //Significant line #1
...
GuildsMasterComponent = __decorate([
        core_1.Component({
            moduleId: module.id,
            selector: 'app-guilds-master',
            templateUrl: 'guilds-master.component.html',
            styleUrls: ['guilds-master.component.css']
        }), 
        __metadata('design:paramtypes', [guild_service_1.GuildService]) //Significant line #2
    ], GuildsMasterComponent);
    return GuildsMasterComponent;

Those two significant lines are generated by Angular compiler based on the argument declaration of the GuildsMasterComponent constructor. If you try to type the "guildService" argument of the constructor as another data type (like "any"), the compiler won't know what object/export it's supposed to use. And you can't set the "guildService" argument to type GuildService without importing GuildService so that the TypeScript compiler can recognize the data type.

Two other tidbits:

  • When I initially created the separate sandbox Angular module (sandbox.module.ts), I did not create a separate routing file with a route configuration for the single sandbox route:  that route was still part of the application Angular module and the app-level routing.  But when I ran the application, that generated an error message that my single sandbox component was "part of the declaration of 2 modules."  Fortunately I found a Stack Overflow post that pointed out the need for separating routing configurations.

  • In an earlier blog post, I noted how my IntelliJ IDE would automatically add the "import {} from ..." statement for any component I added to my router configuration via auto-complete (where IntelliJ would provide me a list of options as I typed the component name, and I could select the one I wanted from the list using the Tab key).  I was happy to see that same convenience feature at work as I added components to the "declarations" list of my app module.

Saturday, August 20, 2016

Augury: An Elegant Tool For Inspecting Angular 2 Applications

In episode 105 of the Adventures in Angular podcast, the panel spoke with one of the developers behind an open-source development tool called Augury. Augury is a Chrome extension that adds an "Augury" tab to the Chrome Development Tools panel, and that tab displays real-time information regarding Angular 2 activity on the current web page.  This information includes:
  • A "Component Tree" list of all of the Angular components and directives currently displayed on the pages.  Actions taken on the page that alter the components included on the page or change the state of a component are highlighted and updated in the Component Tree in real time.

  • A list of properties and providers associated with the selected component or directive in the Component Tree.  For example, you could select a form input and see the current form control status values (dirty, pristine, valid, etc.)

  • A "View Source" link associated with the selected component in the Component Tree, which when clicked will display the source code of the component's TypeScript file in the "Sources" tab of the Chrome Developers Tool.

  • An Injector Graph that displays a diagram of the dependenices injected into the currently selected component.

  • A Router Tree that displays a diagram of all of the routes defined in the application (using this feature does require injecting the Router in the root application component as explained on the Augury GitHub page).

  • A search tool for locating a desired component or directive element.

    • Related to this, Augury adds a custom identifier attribute to each component and directive it displays in the Component Tree, with the id value denoting where the item exists in the hierarchy and its place amongst its sibling elements, making it easy to find, say, link 5 out of 12 in ComponentX

To me, this is a no-brainer, must-have tool for existing and would-be Angular 2 developers.  Install the extension, and then take it for a spin with the "kitchen sink" demo application hosted by the Augury team.  

Wednesday, August 17, 2016

Learning Angular 2: Adding a Master Guild List Component To My Sandbox Application

I recently released version 0.0.2 of my GuildRunner sandbox Angular 2 application.  Changes made during this release include:

  • The addition of a Bootstrap-powered navigation bar (main-navigation.component.ts) which necessitated adding CDN calls for jQuery and Bootstrap JS (index.html).
  • The addition of Angular 2 routing (main.ts, app.routing.ts).
  • The creation of guild data for use by the in-memory web API (db/guilds.ts).
  • The creation of Guild, Address, and Member domain classes to be populated with the guild data (the address.ts, guild.ts, and member.ts files in the "domain" folder) via the GuildService (guild.service.ts).
  • The creation of a master view that displays data from a list of Guild objects within a table (guilds-master.component.ts).
  • The creation of a "sandbox" area of the application where I can keep experimental and diagnostic features (the "sandbox" folder). 

Some lessons learned during the coding of this release:

The in-memory web API has limitations

The in-memory web API is currently limited to mimicking a shallow data graph. It allows you to mock the following types of REST calls:

  • app/guilds (to retrieve all guilds)
  • app/guilds/1 (to retrieve the guild with an id of 1)
  • app/guilds/?name=Blacksmiths (to retrieve the guild or guilds based on the query string)

...but you cannot simulate deeper REST calls:

  • app/guilds/1/members/1 (retrieving the member with id of 1 from guild 1)

For this application at this particular point, it's not a big deal, but I will probably be looking at alternatives methods for faking HTTP calls at some point down the line.

Instantiating domain model classes

In the Tour of Heroes tutorial, the instructions have you create a Hero class with properties for the id and name of the hero. Later, that class is used to declare a component variable with a data type of an array of Heroes:


heroes: Hero[];

...which is then populated with the hero data, an array of object literals:


[
  {id: 11, name: 'Mr. Nice'},
  {id: 12, name: 'Narco'},
  ...
]

...like so:


this.heroService.getHeroes().then(heroes => this.heroes = heroes);

From a coding standpoint, declaring the "heroes" variable as an array of Hero objects ensures that another developer cannot use code to populate that variable with anything but Hero objects, but that declaration is meaningless at runtime. Doing the same thing with my guild data:


//Populates the "guilds" variable with the raw retrieved data (array of object literals)
export class GuildsMasterComponent implements OnInit {

  guilds: Guild[];
  constructor( private guildService: GuildService ) { }

  ngOnInit() {
    this.guildService.getGuilds().then( guilds => this.guilds = guilds )
  }
}

...results in the "guilds" variable being populated with the raw array of guild object literals, each with an address object literal and an array of member object literals. But that's not what I wanted: I wanted an array of Guild objects with included Address and Member objects.

So I wrote the code to instantiate the desired objects, populating the property values via the constructor method:


//guilds-master.component.ts
...
import { Guild } from '../domain/guild';
...
export class GuildsMasterComponent implements OnInit {

  guilds: Guild[] = [];
  
  constructor( private guildService: GuildService ) { }

  ngOnInit() {
    this.guildService.getGuilds().then( guilds => {
      guilds.forEach( guild => {
        this.guilds.push( new Guild( guild ) )
      })
    } );
  }
}

//guild.ts
import { Address } from './address';
import { Member } from './member';

export class Guild {
  id: number;
  name: string;
  expenses: number;
  revenue: number;
  profit: number;

  address: Address;
  members: Member[] = [];

  constructor( guildData:any ) {
    this.id = guildData.id;
    this.name = guildData.name;
    this.expenses = guildData.expenses;
    this.revenue = guildData.revenue;

    this.profit = this.calculateProfit();

    this.address = new Address( guildData.address );

    guildData.members.forEach( member => {
      this.members.push( new Member( member ) );
    }, this );

  }

  calculateProfit() {
    return this.revenue - this.expenses;
  }

}

Providing my master view with an array of Guild objects allowed me to display the profit of each guild in addition to the raw guild data provided by the in-memory web API.

The currency pipe

This release marked my first time using one of the built-in Angular 2 pipes, though it was pretty similar to my experiencing using the built-in filters in Angular 1.

I was a tad surprised that the default CurrencyPipe settings would result in the number being prefixed with "USD" rather than a dollar sign. But a quick glance through the CurrencyPipe documentation gave me the settings I wanted and instructions on how to further control the output with the DecimalPipe:


<td align="right”>{{guild.revenue | currency:'USD':true:'.2-2' }}</td>

In cases where you wanted your application to support internationalization, I imagine you could use a component variable to dynamically affect the currency code:


<td align="right”>{{guild.revenue | currency:currencyCode:true:'.2-2' }}</td>

 

Wednesday, August 10, 2016

IntelliJ, Angular CLI, and Indexing

As I started working on my Angular CLI-managed Angular 2 project, I discovered that making code changes while Angular CLI was either serving my application or waiting to re-execute unit tests would cause my IntelliJ IDE to start re-indexing my project files.  Each indexing run took several minutes and during that time IntelliJ was slow to respond to my attempts to edit and interact with the code files.

I solved this performance issue by selecting the "Project Structure" / "Project Settings" menu item, selecting "Modules", and marking the following folders as "Excluded" on the "Source" tab:

  • dist
  • tmp
Those two folders are created and updated by Angular CLI automatically while testing and serving the application: there's no benefit in having the IDE index them.