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.

Monday, August 8, 2016

Learning Angular 2: Creating My First Sandbox Web Application

While there is still a lot out there for me to read regarding Angular 2, I tend to learn by coding and solving problems.  Even though there are a few aspects of Angular 2 that are in flux at this time (like forms), I feel that I can start writing an application without much fear that I'd have to go back and redo things because the API has changed.

So I've created my first "sandbox" Angular 2 application where I can practice writing Angular code and figure out ways to accomplish specific application tasks with Angular 2.  I'm going to keep a copy of the code up on GitHub and release milestones in my development so that I have a historical picture of the development and so I can potentially backtrack and create different solutions to a given problem.  Plus, it will allow anyone to pull down a tagged version on their own machine to look at the code.

My first sandbox is an application called "GuildRunner".  My plan is that it will be an application for managing a fictional collection of trade guilds, and so I can use it to exploring dealing with common application issues like authentication and authorization, data relationships, and searching.  But I wanted to start by simply creating the foundation for the application structure and getting it up and running.

So I started by creating the repo in GitHub, and then cloned the repo into a new IntelliJ IDEA project via the IntelliJ option for creating projects via GitHub.  I then opened a command prompt in the project directory and invoked the Angular CLI command "ng init" to create the starting project files: I was pleased that the command let me decided whether or not to overwrite the README.md file cloned from GitHub.  Another nice thing about the CLI-generated file I hadn't noticed before:  the .gitignore file is configured to ignore the IntelliJ-specific files as well as the node_modules and typings folder during commits, which is nice.

At that point, I had a basic, single-component app that I could run with the "ng serve" command of Angular CLI.  But I wanted to use the in-memory web API (at least initially) to provide the data for the application as if it was interacting with a server and a database, so I needed to reconfigure the application to utilize that feature.  The details of that reconfiguration ended up as a separate blog post: Adding the In-Memory Web API to a SystemJS-based Angular CLI Application.

In the Tour of Heroes example of using the in-memory web API, the mock data was defined/written out within the InMemoryDataService createDB() method.  Since I plan on creating a fair amount of mock data, I created a "db" folder under the "app" folder that would house all the modules that would export the data collections.  I then created my first bit of mock data: a single version record.


//src/app/db/version.ts

let version = [
  {id: 1, name: '0.0.1'}
];

export { version }

...and provided that to the InMemoryDataService via an import:


//src/in-memory-data.service.ts

import { version } from './db/version';

export class InMemoryDataService {
  createDb() {
    return { version };
  }
}

The purpose of the version record was to have some data to display on the main application page that would confirm that the in-memory web API was working (and also confirm the version of the sandbox application I was working with). So with the Tour of Heroes code as a guide, I created a VersionService to retrieve the version data and a VersionComponent to display it:


//src/app/version/version.service.ts

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

import 'rxjs/add/operator/toPromise';

@Injectable()
export class VersionService {

  private versionUrl = 'app/version'

  constructor( private http: Http ) { }

  getVersion() {
    return this.http.get(this.versionUrl)
      .toPromise()
      .then(response => response.json().data )
      .catch(this.handleError);
  }

  private handleError(error: any) {
    console.error('An error occurred', error);
    return Promise.reject(error.message || error);
  }
}

//src/app/version/version.component.ts

import { Component, OnInit } from '@angular/core';
import { VersionService } from './version.service';

@Component({
  moduleId: module.id,
  selector: 'app-version',
  templateUrl: 'version.component.html',
  providers: [
    VersionService
  ]
})
export class VersionComponent implements OnInit {

  versionNumber = '-.*.-';

  constructor( private versionService: VersionService ) { }

  ngOnInit() {
    this.versionService.getVersion().then( versions => this.versionNumber = versions[0].name );
  }

}

<-- src/app/version/version.component.html -->

<p><strong>Version:</strong> {{versionNumber}}</p>

Then (after adding Bootstrap to the project and adding some Bootstrap layout containers), I added the VersionComponent as a sub-component of the AppComponent:


//src/app/app.component.ts

...
@Component({
  ...
  directives: [
    VersionComponent
  ]
})

<-- src/app/app.component.html -->
<div class="container">
  <div class="row">
    <div class="col-md-10">
      <h1>{{title}}</h1>
    </div>
    <div class="col-md-2 text-right">
      <app-version></app-version>
    </div>
  </div>
</div>

Once all of that was done, I could run my application using "ng serve" and a moment after the page loaded I could see the version number.

I finished up this version of the sandbox by updating the current set of unit and end-to-end (e2e) test files such that they would pass. Previous experience with e2e testing let me update the single e2e test pretty easily, but getting the minimalist unit tests to work took some trial-and-error, and I don't yet have a clear sense of how to set up the unit tests such that the component under test has all the dependencies (or mocks of the dependencies) it needs.

The release of the GuildRunner sandbox that contains the application foundation code and the changes described above can be found on the GuildRunner GitHub repo as release 0.0.1:

https://github.com/bcswartz/angular2-sandbox-guildrunner/releases/tag/0.0.1 

Instructions for running the sandbox on your own machine via Angular CLI can be found on the main page of the GitHub repo.

Thursday, August 4, 2016

Adding the In-Memory Web API to a SystemJS-based Angular CLI Application

8/6/2016 EDIT: On 8/2/2016, the Angular CLI was updated to reflect the fact that the CLI was being refactored to use Webpack instead of SystemJS.  Currently, an npm install of Angular CLI will still give you a version that uses SystemJS, and the following instructions apply to that SystemJS version.

As of version 1.0.0-beta.10, the Angular CLI tool does not provide an option for generating a base Angular 2 application that includes the in-memory web API, which is a tool that lets developers simulate the return of data from HTTP calls.  I figured adding the in-memory web API to my CLI-generated application was just a matter of mimicking how the HTTP lesson in the Tour of Heroes tutorial did it, but it was a bit more involved than that. Here's how you do it.

(From here on out, I'm going to abbreviate "in-memory web API" as IMWA for the sake of brevity. Someone needs to come up with a shorter, cooler name for this tool.)

First, you add the IMWA as a dependency in the package.json file:


"dependencies": {
  ...
  "angular2-in-memory-web-api": "0.0.14"
}

...and then run "npm install" from the command prompt in the directory containing the package.json file to download the IMWA node module (I tend to delete my entire "node_modules" folder first just to make sure everything installs fresh).

Then you add the neccessary imports to your main.ts file and use those imports in the bootstrap() method:


import { XHRBackend } from '@angular/http';

import { InMemoryBackendService, SEED_DATA } from 'angular2-in-memory-web-api';
import { InMemoryDataService }               from './app/in-memory-data.service';

import { HTTP_PROVIDERS } from '@angular/http';

...

bootstrap(AppComponent, [
  HTTP_PROVIDERS,
  { provide: XHRBackend, useClass: InMemoryBackendService },
  { provide: SEED_DATA, useClass: InMemoryDataService }      
]);

Then create a in-memory-data.service.ts file in your src/app directory with some temporary placeholder data:


export class InMemoryDataService {
  createDb() {
    let tempData = [
      {id: 1, name: 'foobar'}
    ];

    return { tempData };
  }
}

Up to this point, all of the setup is nearly the same as it was for the Tour of Heroes tutorial, but the final changes needed are CLI-specific.

When you use the CLI's "ng serve" command to compile and run the application on your local machine, the CLI generates a "dist" directory with all of the necessary files to execute the application. The "dist" directory consists of:

  • The index.html and global configuration files (main.js and system-config.js) in the root of the "dist" folder.
  • The "app" folder which contains the rest of the Angular 2 files specific to your application, from the main component file on down.
  • A "vendor" directory that contains the files from the various node modules needed to run the application, such as the core Angular 2 library files.

The IMWA needs to be included as a separate folder under that "vendor" directory in order for the IMWA to be available to your application.

The "angular-cli-build.js" file in the root of your project filespace (in the same directory as the package.json) controls which node modules make it into the build (the "dist" folder). Add the IMWA to the array of modules like so:


vendorNpmFiles: [
  ...
  'angular2-in-memory-web-api/*.+(js)'
]

The "*.+(js)" syntax ensures that all of the ".js" files in the IMWA node module are copied to the appropriate folder ("angular2-in-memory-web-api") under "vendor" (at this time, there are no ".js.map" files in the IMWA module).

Finally, you need to make sure the IMWA module in the "vendor" directory is loaded by SystemJS along with the regular Angular modules by adding the IMWA to the package configuration in the "system-config.ts" file in the "src" directory of your project:


/** Map relative paths to URLs. */
const map: any = {
  'angular2-in-memory-web-api': 'vendor/angular2-in-memory-web-api'
};

/** User packages configuration. */
const packages: any = {
  'angular2-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
};

That should do the trick: run your app using "ng serve", open up the browser console, and confirm that there are no 404 error messages regarding the IMWA.