Sunday, July 31, 2016

IntelliJ 2016.2 and Angular 2 Support

I realized today that the latest update (version 2016.2) for my IDE of choice - IntelliJ IDEA (Ultimate version) - includes additional support for Angular 2:

  • A collection of live templates for Angular 2 files such as components and services.
  • A better understanding of template syntax.
  • The ability to create a new IntelliJ project via the Angular CLI tool.
So I downloaded and installed the update, but that alone wasn't sufficient to access these new features.  Turns out I had never installed the "AngularJS" plugin (I remember coming across it before, just hadn't installed it).

Once I installed that plugin and restarted IntelliJ, I could select File -> New -> Project from the menu tree, and "AngularJS" (for Angular 1 projects) and "Angular CLI" were new options listed under the "Static Web" project option.  I went ahead and chose "Angular CLI", and IntelliJ invoked the global install of Angular CLI on my laptop and executed the "ng-init" command to create the application structure and foundation files for a new Angular 2 project.

Inside the Angular project, I could create a new component using live templates by creating a new empty TypeScript file, hitting Control/Command-J to insert a live template, typing "ng2-component" until it was the selected template and hitting the Tab key.  The template then lets you tab through the update points in the template so you can enter the directive, template, and component names you want.

Very cool, but I think I would probably end up creating my components using Angular CLI from within the Terminal window in the IDE, because the CLI can generate the full set of files for a given component (the TypeScript file, the template HTML file, the CSS file, and the unit test file).  It also looks like the templates that contain code related to routing need updating.

Still, it's always nice when your IDE adds new features to making your coding a little easier.

First Impressions of Angular CLI

Before creating a demo Angular 2 project of my own from scratch, I decided to play with Angular CLI, the command line tool provided by the Angular team to help streamline Angular 2 development.

There are a number of posts and articles out there about Angular CLI, so I'll only share a few personal observations:

  • The "--help" option for displaying documentation for the overall list of commands or individual commands is well executed:  much more useful and readable than most command line tool documentation.

  • I really like the "dry-run" option provided with the commands that generate the application skeleton and config files.  It lets you see a list of the files and folders that would otherwise be created by the command without actually creating them, giving you an idea of what to expect.

  • The application skeleton structure is a bit different from the structure used in the Quick Start and Tour of Heroes tutorial, moving the "app" directory under an "src" directory.  It does this to make room for a "dist" directory parallel to the "src" directory where it can output runtime files for testing and environment-specific distribution builds.  I found it interesting that it places the "main.ts" file in that "src" directory instead of the "app" directory.

  • I liked how the default development distribution build retains the separate .js and .js.map files for the project code files, while the production build concatenates those files and generally packages your assets to make them more efficient.

  • The "ng generate" command is most useful for generating new components.  The generated component contains the appropriate @angular/core imports and @Component() metadata, contains an empty constructor method, and implements ngOnInit.  Option flags used with the generation command can customize certain aspects of the generated component, such as whether the component uses external HTML and CSS files (the default) verses inline template and inline styles, and whether all of the component assets are bundled in a separate directory.  The other files you can generate with the command are mostly empty shells (although generated service files do include the @Injectable() decorator).

  • I like that the generated components use the module.id technique to handle relative pathing for the templateUrl and styleUrls properties.

  • The CLI documentation refers to the types of files you can generate with "ng generate" as "blueprints".  I hope that means that there will one day be an option to add your own personal blueprints into the mix.

  • It's interesting that "ng test" starts by creating a development build in the "dist" folder before performing the tests...presumably because the tests are run against that folder.  I'm sure there's a reason for doing it that way (making sure everything works after packaging?), but it makes the startup time for testing slow.  On the flip-side, once it's started it watches for file changes and re-tests on the fly, so if your coding process involves running unit tests in the background all the time the start-up time penalty is a one-time cost.  I also wonder what the implications are for performing unit testing via testing tools in your IDE:  would those IDE tools also need to test the build files rather than the .js files in the "app" folder?

Overall, I really like the tool so far, and I look forward to seeing it evolve alongside Angular 2.

Sunday, July 24, 2016

Recognizing TypeScript's Jurisdictional Boundary

While I was exploring the current Angular 2 documentation regarding forms and form field validity, I caught myself wondering why the Angular code wouldn't block or complain about an attempt to assign an incorrect data type value to an object property.  Given the following TypeScript object:


export class Villain {
  id: number;
  name: string;
  age: number;
  dateOfBirth: Date;
}

...you could be forgiven if you thought, for a brief moment at least, that a user entering property values for a Villain via a form would experience an error of some kind if they tried to enter a non-number in the age form field,or a string value of "7/14/84" in the date of birth field.

But of course that wouldn't happen. TypeScript only enforces those types when it compiles the code, preventing programmers from using the wrong data type in the code. That type enforcement is not carried through to the resulting JavaScript.

This is hardly a revelation.  TypeScript is a tool for writing JavaScript: it doesn't alter the base behavior or functionality of JavaScript.  But I can see developers spending a few hours coding classes and service methods in TypeScript, then turning their attention to the code that interacts with the web UI and having to remind themselves that the type protection doesn't apply to user/UI actions that change the class property values.  In that area of the code, you have to enforce the data types with explicit code.

And it made me wonder if there should be a way to carry those data type restrictions on class properties down to the resulting JavaScript code by default.  Not sure how feasible that would be.  I would think you'd have to make each property a private property with getter/setter methods where the setter method would ensure the incoming value met the data type criteria.  But then how would a data type mistmatch be handled?  You probably wouldn't want to throw an error:  you'd want to record the attempt in some readable property.  Would you prevent the property from being set to an invalid value, or would you allow it and count on the developer to write code to inspect the object for validity issues before proceeding?  And how would you provide a mechanism for adding custom validations on top of the data type validations?

No matter how you went about it, you'd end up with an opinionated process for enforcing the data types that probably wouldn't work for everyone, which is probably why TypeScript doesn't do anything like that with the compiled JavaScript code.

Learning Angular 2: Exploring the Current Features of Forms

One of the things I noticed when I completed the official "Tour of Heroes" Angular 2 tutorial was that there wasn't a lesson on using forms:  in Angular 1 input bindings that were managed under ngForm provided data state, validation, and error-handling features, and I had heard that Angular 2 had the same thing.

Apparently forms are another aspect of Angular 2 that is still somewhat of a moving target: the current "Forms" chapter under the "Basics" category of the Angular 2 site documents a deprecated version and points to a newer documentation page.  I decided to read through the newer documentation page and try out the current forms functionality myself, building off of my existing Tour of Heroes project codebase.

The decision to use my Tour of Heroes code ended up causing a few problems.  The first problem I ran into was that I didn't have an "@angular/forms" module to import the form providers from per the documentation instructions.  It's not included in the package.json file used for both Tour of Heroes and the QuickStart.  An exection of "npm view @angular2/forms" told me that the forms module current version was "0.2.0".  I updated package.json, deleted my current node_modules folder, and ran "npm install", and after that I had an "@angular/forms" module folder, and I thought I was in business.

However, I got a 404 error trying to load "@angular/forms" when I tried to run the application.  That one caused some head-scratching until I realized where I went wrong,  Having been several weeks since I set up the Quick Start tutorial and then later having copied over those configuration files, I had forgotten about the role of the systemjs.config.js file.  The array of ngPackageNames determines what packages under the "@angular" node_modules folder are loaded, and "forms" was not in the array.  Once I added it, the error went away and I could actually focus on the exercises in the documentation.

The biggest takeaway from the page was that using the [{ngModel}] binding on a form control leads to that form control being decorated with CSS classes that describe the state of the form control:

  • ng-untouched vs. ng-touched, which indicate if the user has interacted with the form control via the mouse or keyboard.
  • ng-pristine vs. ng-dirty, which indicate whether the value of the form control has changed.
  • ng-valid vs. ng-invalid, which indicates if the value of the form control is valid or invalid.
There are some nuances to those explanations, some of which were explained on the documentation page and some that I determined for myself:
  • The class change from untouched to touched doesn't take place until the form control loses focus after the user has touched it (put the form control in focused stated) with either the mouse of keyboard.

  • The class change from pristine to dirty only takes place if the user changes the value of the form control via the UI, such as by typing or by performing a paste action in the form control.  Changing the value of the model data bound to the input programmatically does not trigger the change from pristine to dirty.

  • The untouched-to-touched and pristine-to-dirty transitions are one-way transitions.  If you delete the last letter of the value in a text input and then restore it (so the value is the same as it was when the text input DOM element was created on the page), the text input is still labeled with ng-dirty.  The document emphasizes this point with an example of how to "reset" the pristine state of the form controls by destroying and recreating the form using ngIf and a conditional, and hints that a proper "form reset" action may be forthcoming.  There is a GitHub issue on the topic:  https://github.com/angular/angular/issues/4933.  Having had some programmatic experience with resetting form values, I'm interested to see how they solve the reset issue.

  • The documentation page demonstrates the transtion of a text input control from validity to invalidity in conjunction with the use of the "required" attribute on the <input> element. But it currently doesn't explain how Angular determines the validity of the form control value under other circumstances.  Angular didn't mark the input as invalid when I entered a non-URL value in an input with the HTML5-supported type of "URL", nor did it react when I entered a value in another text input that exceeded the value of the "min" attribute.  A search through the Angular.io site didn't turn up any page that clearly explained how to perform the validation with the latest implementation of forms, though the references to Validators and their use implies that the answer likely involves applying validation rules/logic programmatically.

The documentation page concluded with code exercises that demonstrated how the form as a whole has a validity state property (courtesy of the ngForm directive that is quietly attached to the <form> tag automatically) which is affected by the validity/invalidity of the individual form control values, and then how the form validity property can be used to block the submission of the form if it's currently invalid.

After going through this page, I can see why forms were not covered as a topic in the Tour of Heroes tutorial.  The implementation of form-related behavior in Angular 2 is still evolving, and though this documentation page illustrates some of the expected behavior and benefits it does so at a basic and somewhat vague level. I'll have to revisit this topic down the road after the documentation is more fleshed out.

Saturday, July 16, 2016

Learning Angular 2: Tour of Heroes Tutorial, Lesson 7

The final lesson of the Tour of Heroes tutorial covers using Angular 2 with HTTP.

It starts off with what seems to be a bit of a contradiction:  it shows how to add the set of HTTP services to the application via the bootstrap invocation in main.ts (following the pattern set to add the router), but then makes a point of mentioning that usually application-wide services are registered in the AppComponent providers (HeroService being the prime example in the tutorial up to this point).  The implication seems to be that if we didn't need to mock the HTTP transactions because we have no actual server to talk with, we could register HTTP_PROVIDERS in the AppComponent, but I wish they had said that explicitly.

The revisions to the HeroService to utilize HTTP calls start with a revision that includes adding "Http" to the new constructor method, but leaves out mentioning the need to import Http into the file.  Same with the Headers class used in the new service methods for updating heroes, so HeroService needs the following imports:


import { Headers, Http } from '@angular/http';

Interesting how the import of the rxjs toPromise() operator is not imported with a variable reference like the rest of the imports.

One thing that's not explained in the lesson is the relationship between the heroesUrl ( 'app/heroes') and the array of heroes in the in-memory-data.service.ts file. According to the in-memory web API documentation, the latter half of the "URL" in this particular call references a key name created in the createDB() method that refers to an array of objects. So changing the HeroService to call from a list of monsters instead of heroes is as easy as:


//in-memory-data.service.ts

createDb() {
  //…The heroes array in the lesson
  let monsters = [
    {id: 11, name: 'Mr. Munch Munch'},
    {id: 12, name: 'Grumpy Pants'},
  ]
  return { heroes, monsters };
}

//hero.service.ts

private heroesUrl = 'app/monsters';

And you can target an individual object in the data array by id value, as is done in the put() and delete() methods described in the lesson.  I wondered though if that meant you were locked into a convention of having an "id" property, so I looked around and found the GitHub page for the in-memory-web-api, which said you could specify the property name to use as the identifier as well as the value ("id" apparently being the default property name if none is specified).  I couldn't get the example syntax where the property name was preceded by a "?" (so maybe the documentation is a tad out-of-date), but I could create a URL targeting a Hero by name instead of id without the "?":


let url = `${this.heroesUrl}/name=${hero.name}`;

The need to add the Content-Type header to every add, update, and delete action to specify the use of JSON caught my eye.  It makes sense given the http methods take an array of headers as an argument, but I could see making a private function that would handle creating and returning that headers array that the http functions could all share. Like so:


private getHeaders(): Headers {
  let headers = new Headers();
  headers.append( 'Content-Type', 'application/json' );
  return headers;
}
//...
private put(hero: Hero) {
  let url = `${this.heroesUrl}/${hero.id}`;

  return this.http
    .put( url, JSON.stringify( hero ), { headers: this.getHeaders() } )
    .toPromise()
    .then( () => hero )
    .catch( this.handleError );
}

The next step of the lesson involves updating the code of the HeroDetailComponent to invoke the public save() method on the HeroService (which calls either the put() or post() private service methods as appropriate).  I don't know why the @Input decorator is applied to the hero property of HeroDetailComponent:  while it's true that later in the lesson HeroDetailComponent is once again made a subcomponent of the HeroComponent, the HeroComponent never passes a Hero object to HeroDetailComponent, and removing the @Input decorator doesn't break any of the new add, update, and delete functionality.

The @Output decorator and the "close" EventEmitter object are a different story.  At first, when I was simply following along with the lesson, I didn't pick up on exactly how the HeroComponent knew to listen for the emission of the saved Hero object that occurs in the HeroDetailComponent goBack() method.  The HeroComponent is coded to react to the "close" EventEmitter of HeroDetailComponent through the event handler put on the directive:


<my-hero-detail (close)="close($event)"></my-hero-detail>

I find it interesting that the argument passed to the close() method of HeroComponent is "$event", and yet what the goBack() method emits and what the close() method expects as its argument is a Hero object. If I change the argument name in the directive from $event to something else, like "sentHero", the incoming argument in the close() method ends up as undefined. In contrast, the delete event handler in HeroComponent passes both a Hero object and an $event event object to the HeroComponent deleteHero() method.

The lesson neglects to mention the need to define a Boolean "addingHero" property to the HeroComponent, but of course an IDE like IntelliJ is quick to point that out.

The delete() method of the HeroService starts with an "event.stopPropagation()" statement.  The lesson doesn't explicitly explain why, but the reason it's there is because the delete button in the UI is contained within each hero <li> block, which all have a click event handler that sets the selected hero.  So the stopPropagation prevents the invocation of the click event that would briefly display the mini-detail UI for the selected hero prior to deletion.

This lesson marks the end of the Tour of Heroes tutorial in its current form.  Overall, I thought it was an excellent introduction to the basic elements involved in creating an application with Angular 2.  I was surprised that it didn't include a lesson on forms, but a quick glance at the overall documentation implies that the forms API is still evolving.  I'll have to explore that on my own in the near future.

Saturday, July 9, 2016

Learning Angular 2: Tour of Heroes Tutorial, Lesson 6

The next lesson in the Tour of Heroes tutorial covers an important topic: routing.  I've been looking forward to this particular lesson because I was curious as to how routing would work given the new component-structured architecture.

The first step of the lesson involves renaming the current AppComponent to HeroesComponent (renaming the file, renaming the exported class) and then creating a new AppComponent in preparation for adding routing.  The most interesting part of this step is the relocation of the HeroService from the HeroesComponent providers array to the new AppComponent providers array, and that the use of the service still works in the HeroesComponent (note that both components still import the service module).  The lesson describes this move as the HeroService being "promoted," and it results in the HeroService becoming a singleton shared with the entire app.

Worth noting that the <base> tag for defining the base route in the header is a single, unclosed tag like the <meta> tags.

As I was entering the routes, I noted that IntelliJ provided suggestions for the name of the component.  

Even better, when I selected the component from the suggestion list, IntelliJ automatically added the needed import statement at the top of the app.routes.ts file: very handy.

The first time I looked at this lesson a few days ago, it was written to utilized the recently deprecated version of the router.  It's since been revised to use the latest version of the router, referred to as the Component Router.  In the instructions for setting up the first route to the HeroesComponent, the lesson doesn't explicitly mention a few of the needed modifications to app.component.ts:

  • The import of ROUTER_DIRECTIVES
  • The removal of the HeroesComponent from the directives array and the removal of the HeroesComponent import
  • The addition of ROUTER_DIRECTIVES to the component directives array

...
import { ROUTER_DIRECTIVES } from '@angular/router';
...
directives: [ ROUTER_DIRECTIVES ],
...

...but those modifications do appear in the "v2" code example for app.component.ts somewhat after the fact.

With the browser console open during the reloading of the app, I see error messages generated about the fact that there is no route for '' - that there is no default route defined yet.  Glancing ahead, that is addressed as part of the next step in the lesson with the instructions for setting up the default route to redirect to the DashboardComponent.  Briefly visited the documentation link for redirects and read about how the pathMatch property for the redirect can be set to a value of "prefix" that is useful in other circumstances.

That's when I ran into a little trouble.  I noticed that IntelliJ did not recognized pathMatch as a valid property for a route (it drew a red line under it).  I put the cursor on the "RouterConfig" in the RouterConfig import statement and used the IntelliJ keyboard shortcut Command-B (Control-B on Windows) to jump to the RouterConfig declaration, which lead me to the TypeScript interface definition for a Route object in the RouteConfig array:


export interface Route {
    path?: string;
    terminal?: boolean;
    component?: Type | string;
    outlet?: string;
    canActivate?: any[];
    canDeactivate?: any[];
    redirectTo?: string;
    children?: Route[];
}

Seeing no "pathMatch" property in the interface (interesting that all of the interface properties, including the "path" property, are optional), I decided to check to see what the latest version of the npm "@angular/router" module was:  turned out I had previously updated package.json to install the 3.0.0-alpha.8 version when I first looked over the lesson, but the latest version was now 3.0.0-beta.2.  So I did the upgrade, and the IntelliJ warning disappeared, but when I tried to load Tour of Heroes, I suddenly had errors in the console and the app wouldn't go past the loading screen.  

I revisted the output from "npm-install" and saw warnings about needing the "rc.4" version of other Angular npm modules (core, common, compiler, platform, platform-browser):  my package.json file was configured to pull down version "rc.2" of those files.  

So I updated my package.json dependencies like so...


 "@angular/common":  "2.0.0-rc.4",
 "@angular/compiler":  "2.0.0-rc.4",
 "@angular/core":  "2.0.0-rc.4",
 "@angular/http":  "2.0.0-rc.2",
 "@angular/platform-browser":  "2.0.0-rc.4",
 "@angular/platform-browser-dynamic":  "2.0.0-rc.4",
 "@angular/router":  "3.0.0-beta.2",

...deleted the node_modules folder in my project to install clean, and ran "npm install" again. I got a different set of warnings at the end of the install (so I'll probably be revising my package.json again in the near future), but Tour of Heroes was up and running again, and "pathMatch" was now a property of the Route interface:


pathMatch?: 'full' | 'prefix';

I had already explored how to pull in the HTML of my component from another file using the "templateUrl" property in a previous post, but this lesson officially introduced that technique. Even better, it provided a link to a documentation page about using component-relative paths: if you're using CommonJS modules and certain module loader systems like SystemJS or webpack, you can add a "moduleId" property to your @Component decorator properties and then reference your HTML or CSS files for the component relative to the path, like so:


@Component({
    moduleId: module.id,
    selector: 'my-dashboard',
    templateUrl: 'dashboard.component.html'
})

I like that approach: it makes it a bit more consistent with the path declarations for the imports and it means you can move the component and its HTML/CSS files to another area of your app and not have to update the templateUrl and styleUrl properties.

The second route defined in the lesson demonstrates the use of route parameters, where the route parameters are defined in a route using the colon (:) in the path just like in Angular 1.  I found the fact that components subscribe to route parameter changes on init and then should unsubscribe to them when the component is destroyed (ngOnDestroy) very interesting.

The section of the lesson that adds the mini-detail introduces one of the built-in pipes for Angular 2.  I remember hearing in a recent Adventures in Angular podcast that pipes in Angular 2 are good for doing some - but not all - of the things one used to do in Angular 1 with filters.  They are primarily output formatters, but are not really designed to perform data manipulation functions like sorting.

The router service's navigate() method replaces the old $location service path() method in Angular 1, but rather than taking a single string argument that represents the path to navigate to, it takes an array of values that starts with the route.  It'll be interesting to see how that plays out in larger applications with deep, multi-parameter routes.