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>

 

No comments:

Post a Comment