Customize EF Core Scaffolding with Handlebars Templates

With the release of Entity Framework Core 2.1 we finally have a version of EF Core that is ready for prime time.  EF Core is a complete re-write of its predecessor Entity Framework 6, which has been married to the full Windows .NET Framework since it was released in 2008 as part of .NET 3 SP1. EF Core, on the other hand, was designed using modern programming concepts such as greater modularity via dependency injection and can run on non-Windows platforms that provide more options for cloud-based deployments that are serverless or use containers.

But writing a new data access stack from scratch required numerous tradeoffs. On the plus side, the EF Core team added long awaited but never realized features, such as mixed client/server query evaluation and composable queries that include raw SQL.  On the downside, they had to cut features considered essential to some teams, such as table-per-type inheritance modeling and support for mapping stored procedures to CUD operations. On balance, however, as of version 2.1 EF Core has been able to achieve much better parity with EF 6 by including previous show-stoppers such as GroupBy query translation and support for System.Transactions. (See this article for a feature-by-feature comparison between EF Core and EF 6.)

While the EF Core runtime has matured to the point where it can be considered a viable option for enterprise applications, the tooling has lagged behind.  For example, there is still no Visual Studio wizard for reverse engineering an existing database to model and context classes.  For that you’ll need to resort to the command line.

Note: Erik Ejlskov Jensen has authored the EF Core Power Tools, which provide a UI for reverse engineering classes from an existing database and use my Handlebars plugin under the covers. They also allow you to perform migrations and visualize your DbContext in various ways.

While that doesn’t present too much difficulty, there has not been a way to customize classes generated by the EF Core tooling.

That is, until now.

I have authored a plug-in (EntityFrameworkCore.Scaffolding.Handlebars) that allows you to use Handlebars templates to customize classes that are reverse engineered from an existing database using the dotnet ef dbcontext scaffold command.  To use the plugin simply add my extension NuGet package to your EF Core Class Library project, then add a class that implements IDesignTimeServices by calling the AddHandlebarsScaffolding extension method that hangs off IServiceCollection.


public class ScaffoldingDesignTimeServices : IDesignTimeServices
{
public void ConfigureDesignTimeServices(IServiceCollection services)
{
var options = ReverseEngineerOptions.DbContextAndEntities;
services.AddHandlebarsScaffolding(options);
}
}

Next, open a command prompt at the project level and use the .NET Core CLI to reverse engineer a context and models from an existing database.  For example, if you have downloaded scripts to create the NorthwindSlim sample database for SQL Server LocalDb, you can run the following command:


dotnet ef dbcontext scaffold "Data Source=(localdb)\MSSQLLocalDB; Initial Catalog=NorthwindSlim; Integrated Security=True" Microsoft.EntityFrameworkCore.SqlServer -o Models -c NorthwindSlimContext -f –context-dir Contexts

The first time you run the command you’ll see a CodeTemplates folder magically appear in your project.

hbs-scaffolding-sample

Handlebars Templates

There you’ll find Handlebars templates for context and entity classes which you can customize to your heart’s content.


{{> dbimports}}
namespace {{namespace}}
{
public partial class {{class}} : DbContext
{
{{> dbsets}}
{{#if entity-type-errors}}
{{#each entity-type-errors}}
{{spaces 8}}{{{entity-type-error}}}
{{/each}}
{{/if}}
{{{on-configuring}}}
{{{on-model-creating}}}
}
}

view raw

DbContext.hbs

hosted with ❤ by GitHub

Notice there are also partial templates that you can also customize.  (One reason you might want to customize generated entity classes would be to implement an interface or extend a base class.)  The next time you run the scaffolding command, you’ll see your changes reflected in the generated classes.

Lastly, my plug-in allows you to register Handlebars helpers for further customizing output based on runtime conditions. Simple pass one or more named tuples to the AddHandlebarsScaffolding extension method.


public class ScaffoldingDesignTimeServices : IDesignTimeServices
{
public void ConfigureDesignTimeServices(IServiceCollection services)
{
// Generate both context and entitites
var options = ReverseEngineerOptions.DbContextAndEntities;
// Register Handlebars helper
var myHelper = (helperName: "my-helper", helperFunction: (Action<TextWriter, object, object[]>) MyHbsHelper);
// Add Handlebars scaffolding templates
services.AddHandlebarsScaffolding(options, myHelper);
}
// Sample Handlebars helper
void MyHbsHelper(TextWriter writer, object context, object[] parameters)
{
writer.Write("// My Handlebars Helper");
}
}

Then insert the helper into your Handlebars template as in the following example.


public partial class {{class}} {{my-helper}}
{
{{{> constructor}}}
{{> properties}}
}

This will result in the helper rendering your desired content, as shown in the following example.


public partial class Category // My Handlebars Helper
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public ICollection<Product> Product { get; set; }
}

view raw

helper-class.cs

hosted with ❤ by GitHub

For further information and detailed instructions please visit the ReadMe on the project repo. Enjoy!

Posted in Technical | Tagged , | 14 Comments

It’s here! Trackable Entities for EF Core!

The idea behind my open source Trackable Entities project is quite simple: track changes to an object graph as you update, add and remove items, then send those changes to a back end service where they can be saved in a single transaction.  It’s an important thing to be able to do, because it’s difficult to wrap multiple round trips in a single transaction without holding locks for a long time.  On the other hand, you could break up related operations into multiple transactions, but then you lose the benefit of atomicity, which enables you to roll back all the changes in a transaction should one of them fail.

To get started with Trackable Entities for Entity Framework Core, download the NuGet package and check out the project repository.  You can also clone the sample applications and follow the instructions.

Brief Introduction

For example, you update an order with related details.  Some details are unchanged, while others are modified, added or removed.  All these changes should be atomic, that is, they should all succeed or none.  The problem with regard to Entity Framework is that, in the context of a web service, updates must be conducted in a disconnected manner, which means that you need to inform EF about which entities require updating and what kind of changes it needs to perform.  In the case of an order with multiple details that need to be updated all at once, there needs to be a way to communicate this information to the web service so that you can attach entities to a DbContext and individually set the state of each entity.  The way Trackable Entities accomplishes this is by means of a simple enum called TrackingState.

public enum TrackingState
{
Unchanged,
Added,
Modified,
Deleted
}

To track changes, entities implement an ITrackable interface, which includes a ModifiedProperties property to support partial updates.

public interface ITrackable
{
TrackingState TrackingState { get; set; }
ICollection<string> ModifiedProperties { get; set; }
}
view raw ITrackable.cs hosted with ❤ by GitHub

In addition, Trackable Entities provides an IMergeable interface to correlate updated entities so they can be merged back into the original object graph on the client.

public interface IMergeable
{
Guid EntityIdentifier { get; set; }
}
view raw IMergeable.cs hosted with ❤ by GitHub

Once entities have been marked as Added, Modified or Deleted, all you have to do is call the ApplyChanges extension method for DbContext.

// Inform EF about changes in an object graph
context.ApplyChanges(order);
// Persist all changes in a single transaction
context.SaveChanges();
view raw ApplyChanges.cs hosted with ❤ by GitHub

Trackable Entities for EF Core

Because Trackable Entities is an extension of Entity Framework, it has only been available for the full .NET Framework running on Windows.  But now that EF has been ported to .NET Core and can run on Linux and MacOS, it’s time for Trackable Entities to come along for the ride!  This makes it possible to create a Web API with ASP.NET Core that can run both locally on a Mac and in a Docker container running on a Linux VM in the Cloud.  How cool is that?!

To get started you can either use Visual Studio for Windows, Visual Studio for Mac, or if you’re adventurous, Visual Studio Code.  It doesn’t matter which option you choose, because in the end your web app will run anywhere .NET Core will run.  In this blog post I’ll walk you through a demo using VS for Mac — just for fun.  But if you prefer, feel free to check out the sample I created using the “classic” version of Visual Studio on Windows.

Note: You’ll need to install the SDK for .NET Core 2.0 or higher.

Start by creating a new project in VS using the ASP.NET Core Web API project template.

aspnet-core-project.png

Then add a .NET Standard Class Library for server-side trackable entities.

net-standard-project.png

Add the TrackableEntities.Common.Core (pre-release) and System.ComponentModel.Annotations NuGet packages.

te-common-core.png

Add classes that implement ITrackable and IMergeable interfaces. You’ll also need to add a using directive for System.ComponentModel.DataAnnotations.Schema, so that you can decorate TrackingState, ModifiedProperties and EntityIdentifier properties with a [NotMapped] attribute, to indicate these properties do not belong to the database schema.

public class Product : ITrackable, IMergeable
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public int? CategoryId { get; set; }
public decimal? UnitPrice { get; set; }
public bool Discontinued { get; set; }
public byte[] RowVersion { get; set; }
public Category Category { get; set; }
[NotMapped]
public TrackingState TrackingState { get; set; }
[NotMapped]
public ICollection<string> ModifiedProperties { get; set; }
[NotMapped]
public Guid EntityIdentifier { get; set; }
}
view raw Product.cs hosted with ❤ by GitHub

EF Core Migrations

Although it is possible to generate entities based on database tables (Database First), in this demo we’ll go the other way and start with entities that will be used to generate database tables and relationships (Code First).  Both approaches use the EF .NET Core CLI, which you install by adding the Microsoft.EntityFrameworkCore.Design package and manually editing the .csproj file for the Web API project to insert a DotNetCliToolReference and change the project target from netstandard2.0 to netcoreapp2.0 (which is required to run the EF CLI). You’ll also need to add a package for the EF provider you’re using, which in this case is Microsoft.EntityFrameworkCore.Sqlite, as well as a reference to the server-side entities project you created earlier.  Then run dotnet restore from the command line.  (Visual Studio does not yet automatically restore tooling packages.)  Here is what your .csproj file will then look like.

<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.0" />
<PackageReference Include="TrackableEntities.EF.Core" Version="1.0.0-beta" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TrackableEntities.Core.Sample.XPlat.Entities.WebApi\TrackableEntities.Core.Sample.XPlat.Entities.WebApi.csproj" />
</ItemGroup>
</Project>

Next, add a specific DbContext-derived class to the Web API project.

public class NorthwindSlimContext : DbContext
{
public NorthwindSlimContext(DbContextOptions<NorthwindSlimContext> options) : base(options) { }
public DbSet<Category> Categories { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderDetail> OrderDetails { get; set; }
public DbSet<Product> Products { get; set; }
}

Lastly, you’ll need to add a class that implements IDesignTimeDbContextFactory to create your context class with the appropriate options and connection string.

public class NorthwindSlimContextFactory : IDesignTimeDbContextFactory<NorthwindSlimContext>
{
public NorthwindSlimContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<NorthwindSlimContext>();
optionsBuilder.UseSqlite("Data Source=northwindslim.db");
return new NorthwindSlimContext(optionsBuilder.Options);
}
}

Now all you need to do to create a database from your entities is to execute the following two commands, after which a northwindslim.db file will appear in the project directory.

dotnet ef migrations add initial
dotnet ef database update

Configure Dependency Injection

To inject your EF context class into controllers, you’ll need to register it with ASP.NET Core’s dependency injection system.  Add code to the ConfigureServices method of the Startup class in which you call services.AddDbContext, passing options that include the provider and connection string.  You’ll also want to configure the JSON serializer to preserve references in order to accommodate cyclical references in object graphs.

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddJsonOptions(
options => options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.All);
services.AddDbContext<NorthwindSlimContext>(
options => options.UseSqlite("Data Source=northwindslim.db"));
}

Web API Controllers

Next add the TrackableEntities.EF.Core NuGet package to the Web API project.  Then you can add a controller to the project in which you use LINQ to execute queries against the database and return objects from your GET actions.

[Produces("application/json")]
[Route("api/Customer")]
public class CustomerController : Controller
{
private readonly NorthwindSlimContext _context;
public CustomerController(NorthwindSlimContext context)
{
_context = context;
}
// GET: api/Customer
[HttpGet]
public async Task<IActionResult> GetCustomers()
{
var customers = await _context.Customers
.ToListAsync();
return Ok(customers);
}
// GET: api/Customer/ALFKI
[HttpGet("{id}")]
public async Task<IActionResult> GetCustomer([FromRoute] string id)
{
var customer = await _context.Customers.SingleOrDefaultAsync(m => m.CustomerId == id);
if (customer == null)
return NotFound();
return Ok(customer);
}
}

Then to utilize Trackable Entities for persisting object graphs with changed entities, all you need to do is call context.ApplyChanges.

// PUT: api/Order
[HttpPut]
public async Task<IActionResult> PutOrder([FromBody] Order order)
{
// Apply changes to context
_context.ApplyChanges(order);
try
{
// Persist changes
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!_context.Orders.Any(o => o.OrderId == order.OrderId))
return NotFound();
throw;
}
// Populate reference properties
await _context.LoadRelatedEntitiesAsync(order);
// Reset tracking state to unchanged
_context.AcceptChanges(order);
// Return updated order
return Ok(order);
}

Note that after calling context.SaveChangesAsync() there is a line of code that calls context.LoadRelatedEntitiesAsync, passing the root entity.  This will traverse the object graph, loading reference properties so that entities returned to the client will have them populated.  For example, when updating an Order it isn’t necessary to send the entire Customer entity when all you have to do is set the order’s CustomerId property.  But the client will usually want to have the Customer property populated when it is returned from the service, which is what LoadRelatedEntitiesAsync does. Lastly, the call to context.AcceptChanges is there to reset TrackingState on each entity to Unchanged, so that the client can can have a fresh start before making additional changes.

You can now execute dotnet run on the command line and the Web API will start listening for requests on the default port 5000.

dotnet-run.png

You can then open a browser and navigate to an API endpoint to make sure you can retrieve entities.  You can also use a REST client such as Postman to make POST, PUT and DELETE requests.

customer-alfki.png

Trackable Entities on the Client

On the client side Trackable Entities provides change-tracking for .NET apps by means of a ChangeTrackingCollection.  You can create a .NET Core console app to which you can add the TrackableEntities.Client NuGet package. Even though this package was written for the full .NET Framework version 4.6.1, you can use it in a .NET Core app because both are compliant with .NET Standard 2.0.

Because client-side entities have a different set of concerns than server-side entities, firing events on property changes and using change tracking collections for navigation properties, you’ll probably want to generate client entities from the database schema you created earlier using the EF Core CLI tooling.  The easiest way to accomplish this is to use T4 templates.  Fortunately, Trackable Entities has a package for that — TrackableEntities.CodeTemplates.Client.Net45.  You’ll need to add it to a traditional .NET Class Library project in Visual Studio for Windows, so that you can add an ADO.NET Entity Data Model that will use it to generate the client entities.

You can then write client-side code that changes entities, gets only the changed entities, and sends them to your Web API service for applying changes and saving them to the database.  See the sample app’s repository for the complete code.

To run the console app, keep the Web API running and execute dotnet run.  Follow the prompts to retrieve, add, update and delete entities at various places in the object graph.

console-client.png

The cool thing is that all this is happening on a Mac, and there’s nothing to stop you from deploying it to a container service such as Amazon EC2, Microsoft Azure or Google Container Engine.  With Trackable Entities and EF Core the future is now.

Posted in Technical | Tagged , , , | 3 Comments

React to JavaScript object updates with observable-entities-js

I just published my first TypeScript library — observable-entities.  It contains base classes that notify observers when properties are updated and when objects are added or removed from collections.

The code for observable-entitites-js can be found here: https://github.com/TrackableEntities/observable-entities-js.

Node sample app that uses observable-entities for reacting to entity updates and when objects are added or removed from set and map collections: https://github.com/tonysneed/hello-observable-entities

Angular sample app that uses observable-entities for reactive binding: https://github.com/TrackableEntities/observable-entities-js-sample.

Getting Started

To get started using observable-entities-js simply add it using NPM as a runtime dependency to your JavaScript application or library.

npm i --save observable-entities

Note: observable-entities uses features of ES2015 that are not compatible with older browsers such as Internet Explorer.

To use observable-entities all you have to do is derive your model classes from ObservableEntity and add a constructor that returns a call to super.proxify.

import { ObservableEntity } from 'observable-entities';

class Product extends ObservableEntity {
  productName: string;
  constructor(productName: string) {
    super();
    this.productName = productName;
    return super.proxify(this);
  }
}

This allows you to create a listener that can subscribe to notifications that take place when any property in the entity is modified.

import { INotifyInfo } from 'observable-entities';
import { Subject } from 'rxjs/Subject';

// Create listener that logs to console when entity is updated
const modifyListener = new Subject[INotifyInfo]();
modifyListener.subscribe(info => {
  console.log(`Entity Update - key: ${info.key} origValue: ${info.origValue} currentValue: ${info.currentValue}`)
});

Then add the listener to the modifyListeners property of the entity.

// Create product and add listener
const product = new Product('Chai');
product.modifyListeners.push(modifyListener);

When a property is set on the entity, the listener will get notified of the property change.

// Set productName property
product.productName = 'Chang';

// Expected output:
// Entity Update - key: productName origValue: Chai currentValue: Chang

If you were to debug this code, you could set a breakpoint on the line of code that logs to the console, and you would hit the breakpoint as soon as you set productName to ‘Chang’.

obs-entities-debug

You can clone the Node demo app I wrote for this post and try debugging it yourself.

Observable Proxies

You may have noticed that modifyListener is of type Subject[INotifyInfo], which is a class that comes from RxJS (Reactive Extensions for JavaScript), a library that represents a style of programming combining the observer design pattern with functional programming.  While it’s possible to design a class that can notify listeners of property changes, it would involve a lot of repetitive boilerplate code as in, for example, this version of the Product class.

Note: To render TypeScript generics in WordPress, it is necessary to use square brackets [] instead of angle brackets <>.

class Product {

  private _productName: string;
  readonly modifyListeners: Subject[INotifyInfo][] = [];

  get productName() {
    return this._productName;
  }

  set productName(value: string) {
    // Notify listeners of property updates
    const notifyInfo: INotifyInfo = { key: 'productName', origValue: this._productName, currentValue: value };
    this.modifyListeners.forEach(listener => listener.next(notifyInfo))
    this._productName = value;
  }

  constructor(productName: string) {
    this._productName = productName;
  }
}

While you could encapsulate the notification code in a protected notify method placed in a base class, you would still need to call the method from the property setter on each property, which is similar to how INotifyPropertyChanged is usually implemented in C# to support two-way data binding.

A cleaner solution would be to intercept calls to property setters in an entity so that you could notify listeners of changes in a generic fashion.  This is why ES2015 proxies were created.  The way it works is that you create a handler that has a trap for the kind of operation you want to intercept, set for example. Then you can do whatever you want when a property is set, such as notify listeners of property changes.

The way observbable-entities does this is by providing a protected proxify method in the ObservableEntity base class, which returns a proxy of the entity.  (Keep in mind that proxies are an ES2015 feature that is not supported by downlevel browsers, such as Internet Explorer — therefore, you’ll need to target ES2015 in apps or libraries that use observable-entities.

protected proxify[TEntity extends object](item: TEntity): TEntity {
  if (!item) { return item; }
  const modifyListeners = this._modifyListeners;
  const excludedProps = this._excludedProperties;
  const setHandler: ProxyHandler[TEntity] = {
    set: (target, property, value) => {
      const key = property.toString();
      if (!excludedProps.has(key)) {
        const notifyInfo: INotifyInfo = { key: key, origValue: (target as any)[property], currentValue: value };
        modifyListeners.forEach(listener => listener.next(notifyInfo));
      }
      (target as any)[property] = value;
      return true;
    }
  };
  return new Proxy[TEntity](item, setHandler);
}

If the constructor of the subclass returns a call to super.proxify, then consumers get a proxy whenever they new up the entity, without the need to create the proxy manually.  (There a static factory method is provided if you have a need for this.)

Observable Sets and Maps

Besides notifications of property updates, observable-entities allows you to receive notifications when entities are added or removed from Set and Map collections. That’s the purpose of ObservableSet and ObservableMap classes, which extend Set and Map by overriding add and delete methods to notify listeners.

You can, for example, create listeners that write to the console when entities are added or removed from an ObservableSet.

// Observe adds and deletes to a Set
const productSet = new ObservableSet(product);

// Create listener that writes to console when entities are added
const addListener = new Subject[INotifyInfo]();
addListener.subscribe(info => {
  console.log(`Set Add - ${(info.currentValue as Product).productName}`)
});
productSet.addListeners.push(addListener);

// Create listener that writes to console when entities are removed
const removeListener = new Subject[INotifyInfo]();
removeListener.subscribe(info => {
  console.log(`Set Remove - ${(info.currentValue as Product).productName}`)
});
productSet.removeListeners.push(removeListener);

Those listeners will then be notified whenever entities are added or deleted from the Set.

// Add entity
const newProduct = new Product('Aniseed Syrup');
productSet.add(newProduct);

// Expected output:
// Set Add - Aniseed Syrup

// Remove entity
productSet.delete(newProduct);

// Expected output:
// Set Remove - Aniseed Syrup

ObservableMap works the same way as ObservableSet, but with key-value pairs.  Note that INotifyInfo also provides the entity key when the listener is notified.

// Observe adds and deletes to a Map
const productMap = new ObservableMap([product.productName, product]);

// Add listener for when entities are added
const addListenerMap = new Subject[INotifyInfo]();
addListenerMap.subscribe(info => {
  console.log(`Map Add - ${info.key} (key): ${(info.currentValue as Product).productName} (value)`)
});
productMap.addListeners.push(addListenerMap);

// Add listener for when entities are removed
const removeListenerMap = new Subject[INotifyInfo]();
removeListenerMap.subscribe(info => {
  console.log(`Map Remove - ${info.key} (key): ${(info.currentValue as Product).productName} (value)`)
});
productMap.removeListeners.push(removeListenerMap);

// Add entity
productMap.add(newProduct.productName, newProduct);

// Expected output:
// Map Add - Aniseed Syrup (key): Aniseed Syrup (value)

// Remove entity
productMap.delete(newProduct.productName);

// Expected output:
// Map Remove - Aniseed Syrup (key): Aniseed Syrup (value)

Angular Data Binding with Observables

One possible application of observable-entities is to use them with Angular’s OnPush change detection strategy so that you can control when change detection cycles take place when binding components to templates.  This could result in faster data binding because you’ll call markForCheck on an injected ChangeDetectorRef so that Angular will perform change detection on that component while skipping others.

Instead of writing to the console, add and modify listeners will call markForCheck.

// Trigger data binding when item is added
this.addListener = new Subject[INotifyInfo]();
this.addListener.subscribe(info => {
  this.cd.markForCheck();
});

// Add listener to products
this.products.addListeners.push(this.addListener);

// Trigger binding when item is updated
this.modifyListener = new Subject[INotifyInfo]();
this.modifyListener.subscribe(info => {
  this.cd.markForCheck();
});

// Add listener to each product
this.products.forEach(product => {
  product.modifyListeners.push(this.modifyListener);
});

To see data binding with observable entities in action you can clone the Angular demo app I wrote for this post.

obs-entities-angular

There are many possible uses for observable entities, sets and maps, and I hope you find my observable-entities-js library useful and interesting.

Enjoy!

Posted in Technical | Tagged , , | Leave a comment

Introducing TypeScript Library Starter Lite

Some time ago I wrote a blog post series on Getting Visual Studio Code Ready for TypeScript.  The ultimate goal was to build a TypeScript library with Visual Studio Code by stitching together all the pieces you needed to compile TypeScript into JavaScript, produce TypeScript declarations to enable intellisense for consumers of your library, and provide automated unit testing with Jasmine and Karma with a build server such as Travis CI.  Gulp was the engine automating all these tasks, and I created a Yeoman generator to let you scaffold a shiny new TypeScript library project with very little effort.

See the code for TypeScript Library Starter Lite here: https://github.com/tonysneed/typescript-library-starter-lite

See a sample app produced by TypeScript Library Starter Lite here: https://github.com/tonysneed/ts-lib-starter-lite-demo

As compelling as this vision seemed to be at the time, it involved a lot of interconnected moving parts that could easily get out of sync, break down and interrupt your development workflow.  The gulpfile alone contained nearly 300 lines of code with a config file containing another 150 lines of code.  And many of the parts were being deprecated frequently and had to be constantly updated.

So when it came time for me to write a bona fide TypeScript Library, I started to look elsewhere.  It was then that I ran across an interesting project called TypeScript Library Starter, which looked quite promising.  Simply by cloning the repo and running npm install, you get a TypeScript library that you can test, lint, build and publish to npm.  So I gave it a test drive and found it mainly lived up to its promises.  However, I found it a bit too opinionated for my taste because it included things like conventional commits and automated semantic releases, which I feel are a too constraining for me personally.  For example, based on the Git commit message, the package is automatically versioned and published to npm, which scared me somewhat. And I was unable to get it to publish pre-release versions (alpha, beta, rc, etc).

On the plus side, building the project produces a dist folder with an entry point for your exported modules, TypeScript declaration files for intellisense, and documentation using jsdoc style comments in your code.  Nonetheless it lacked a few things I like to have in my projects, such as linting with Codelyzer using Angular Style Guide rules and config files to support testing and debugging with keyboard shortcuts for Visual Studio Code.

So I decided to Fork the author’s repo to create my own version without some of the more opinionated features, while including other things I find appealing.

Getting Started

Just like the original TypeScript Library Starter, to use my “lite” version all you have to do is clone the repo and then run npm install.

# Clone the repo into a folder with your project name
git clone https://github.com/tonysneed/typescript-library-starter-lite.git YOURFOLDERNAME
cd YOURFOLDERNAME

# Run npm install and write your library name when asked
npm install

When prompted for a library name, you can either use the folder name where the project is located or another name of your choosing. Keep in mind that this will be your package name when publishing to npm.

ts-lib-starter-setup

If you open the project in an editor such as VS Code, you’ll notice a series of scripts in the package.json file.  The main ones you’ll be using are lint, test and build.  If you take a peak inside the .vscode folder, you’ll see key bindings for both Mac and Windows. If you’d like to associate the npm scripts with some standard keyboard shortcuts, just copy the contents of either file, then select the Keyboard Shortcuts menu under Preferences, click the keybindings.json link you see there, and paste the shortcuts you just copied.  Then you can simply press Ctrl+Shift+L to lint your code, Cmd+Shift+T (Mac) or Ctrl+Shift+T (Windows) to run unit tests, and Cmd+Shift+B (Mac) or Ctrl+Shift+B (Windows) to build and produce output to the dist folder.  All of these will execute in the integrated terminal in VS Code, and you can terminate a running task (such as test) by pressing Cmd+Shift+X (Mac) or Ctrl+Shift+X (Windows).  To get even more goodness from VS Code, I recommend you also install some extensions: vscode-icons (pretty icons in the Explorer), TSLint (real-time linting), TypeScript Hero (organizing imports) and Move TS (renaming/moving files).

The default TypeScript target in my “lite” starter kit is ES2015, so you can take advantage of advanced JavaScript features (such as proxies) not available in legacy browsers (i.e., Internet Explorer). However, to get the broadest reach, you can elect to target ES5, either by changing the target to “es5” in tsconfig.json (as well as the “module” suffix in package.json), or you can simply clone from target-es5 branch in my starter kit repo.

If you take a peek inside the src directory, you’ll see a few files placed there to get you started.  The dummy.ts file contains a DummyClass with a getPerson method, and the dummy.spec.ts file has a Jasmine test that new’s up a Person class, which is imported from a person.spec.ts file inside the models folder.  (The reason the model file has a spec suffix it to prevent it from inclusion in the generated docs.)

ts-lib-src

For exposing classes and other constructs from your library, there’s the TypeScript file that has the same name as your project.  If you open the file, you’ll see an export for the Dummy class, which you can replace with exports from your own modules for classes in your library.

export { Dummy } from './dummy';

Linting, Testing, Debugging, Building

When you run the lint task, the starter kit applies a set of rules to make sure your code uses standard TypeScript conventions.  I like to use the Angular style guide, so I configured TSLint to use the same rules as for Angular applications.

The starter kit uses a test runner called Jest, which is a JavaScript testing framework developed by Facebook that has zero configuration and be default only runs tests that have changed since your last commit, unless you specify all tests or apply a filter.

ts-lib-tests

I’ve included support in the starter kit for debugging Jest tests with a launch.json file located in the .vscode folder.  All you have to do is set a breakpoint in a spec file, start the tests by pressing Shift+Cmd+T (Mac) or Shift+Ctrl+T (Windows), then press F5 to start debugging.  You should then hit your breakpoint, where you’ll have all the debugging goodness of VS Code at your disposal, including stepping into code, adding watches, viewing local variables, inspecting the call stack, and interacting with the Debug Console.

ts-lib-debug

When you build the project, a dist folder is created that contains the output files for your library, TypeScript definition files, and docs that use jsdoc comments from your source files and that will be published to a GitHub Pages branch in your repository.  I recommend a VS Code extension called Document This for adding jsdoc comments with a keyboard shortcut, which allows them to show up in intellisense for someone consuming your library.

ts-lib-intellisense

Continuous Integration

TypeScript Library Starter comes with support for continuous integration with Travis CI, but you first need to create an account in Travis if you don’t already have one.  Then you need to add your GitHub repo to your Travis account, so that pushing commits and creating pull requests will trigger a CI build that runs your tests and updates the docs.  Then you’ll need to prepare your repo to use semantic-release to allow Travis to publish your docs.

To get this to work you need to perform a few simple steps.  First, make sure you have published your project to GitHub.  I find that using the GitHub Desktop app is the easiest way to do this.  Then make sure to copy the clone URL and paste it into the repository “url” field in the package.json file.  Next, run the following command to prepare the CI hooks.

npm run semantic-release-prepare

Then install semantic release and run it, answering “No” when prompted to create a travis.yml file.

npm install -g semantic-release-cli
semantic-release-cli setup

Note that you’ll need to create an npm account so that you can enter your npm credentials when prompted. However, these credentials won’t be used in this starter kit because automatic releases have been disabled.

Once you’ve taken these steps, pushing a commit or merging a PR to your master branch will kick off a CI build that runs your tests and publishes your docs.

travis-build

To see where the docs have been published, go to the Settings for your GitHub repository and note the GitHub Pages url shown there.  Here’s a sample of what the docs generated by typeDoc look like.

ts-lib-demo-docs

Publishing

The full-featured starter kit provides for automatic releases and publishing to npm, with a changelog written to an auto-generated release on your GitHub repo — but I like to retain complete control over my releases.  So with TypeScript Library Starter Lite, you’ll want to create a versioned release on your GitHub repo with a list of issues addressed by that release. To help with this process you can create milestones and assign issues to them, as well as use a project management tool, such as GitHub Projects or ZenHub.

Before publishing your library to npm, I recommend you first test it locally.  But rather than using npm link and unlink, which didn’t work for me consistently, I use npm pack to test my library locally before publishing to npm.  This creates a tarball named after the name of the project, plus the version specified in the package.json. Then you can install the package, for example in an Angular test app.

cd path/to/your/project/
npm install --save ../path/to/your/npm/packagename-version.tgz

When you’re ready to release your wonder to the world, you can publish your package to npm with the npm publish command.

npm publish

After supplying your npm credentials, your package will magically appear on npm for all the world to see and use.

npm-publish

If you make a mistake, you can just bump the version number, or if desired you can unpublish one or all versions of your package and publish it again. (Make sure no one else has installed your package!)

With the power of TypeScript Library Starter Lite (or the full featured starter kit), you can test, build and publish your own TypeScript libraries with ease and confidence.  A great deal of thanks goes to @alexjoverm creating an amazing TypeScript library starter kit that I was able to tailor and streamline.

Enjoy!

Posted in Personal | Tagged , | Leave a comment

Turbocharge VS Code for Angular Development

I like using Visual Studio Code for my Angular development, because it is TypeScript-friendly, supports in-editor Chrome Debugging, and has numerous extensions for boosting productivity.  In this post I’d like to show how you can turbocharge VS Code to make it an even more powerful tool for developing Angular applications.

angular-ts-code

Tasks and Keyboard Shorcuts

Nowadays the best way to build an Angular app is using Angular CLI.  Not only can you use it to scaffold a new app and to generate modules, components and services, but there are commands you use all the time for linting, building, and serving the app, as well as running unit and end-to-end tests.  That usually means having to toggle back and forth between Visual Studio Code and the Terminal.

Get the code this this blog post here: https://github.com/tonysneed/vscode-ng

The integrated terminal in Visual Studio Code allows you do this from within the editor, but you still have to type all your commands into the terminal window and manually create new windows to run multiple commands simulataneously.

vs-code-terminal.png

It would be a big time-saver if you could use keyboard shortcuts to execute the npm scripts for Angular in package.json.  Here are scripts added by Angular CLI when you create a new application.

{
  "name": "my-dream-app",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },

You should generally run “npm start” rather than “ng serve,” because the npm script will use the version of @angular/cli that is exactly specified in project.json, whereas “ng serve” uses the CLI version that is installed globally and is frequently updated.  If you use the local CLI version, then your app should be insulated from dependencies may change when the global CLI is updated.

The way to do this is first to create tasks in VS Code that execute these scripts.  Then you can assign keyboard shortcuts to run each task.  Version 2.0.0 of the tasks.json schema is more flexible and powerful than the prior version.  For example, you can define tasks that natively run npm scripts.  The “build” and “test” scripts are good candidates for this because they are natively mapped by VS Code to standard key bindings (Shift+Cmd+B and Shift+Cmd+T respectively).

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "type": "npm",
            "script": "build",
            "presentation": {
                "reveal": "always"
            },
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": [
                "$tsc"
            ]
        },
        {
            "label": "test",
            "type": "npm",
            "script": "test",
            "presentation": {
                "reveal": "always"
            },
            "group": {
                "kind": "test",
                "isDefault": true
            }
        },

However, to assign keyboard shortcuts to other tasks, you need to use the “shell” type to specify a task name, which is required in order to assign a keyboard shortcut.  Note that “args” has been deprecated, so you need to concatenate arguments to the “command” string.  NPM scripts accept parameters if you preceed them with a double hyphens.  For example, you can use this approach to pass an “-o” argument, so that the app is opened in a browser when it is served.

{
    "taskName": "serve",
    "type": "shell",
    "command": "npm start",
    "problemMatcher": [
        "$tsc"
    ]
},
{
    "taskName": "open",
    "type": "shell",
    "command": "npm start -- -o",
    "problemMatcher": [
        "$tsc"
    ]
},
{
    "taskName": "lint",
    "type": "shell",
    "command": "npm run lint",
    "problemMatcher": [
        "$tsc"
    ]
},
{
    "taskName": "e2e",
    "type": "shell",
    "command": "npm run e2e"
}

Here are shortcuts you can paste into your keybindings.json file.  Note the shortcut to terminate a running task by pressing Shift+Cmd+X.  If you run multiple tasks at the same time, you will be prompted to select which task you want to end.  Also note I assigned Shift+Cmd+O to opening the app, so the shortcut for “Go to Symbol” needs to be assigned to another shortcut (Ctrl+Shift+O).

// Place your key bindings in this file to overwrite the defaults
[
    // End a running background task
    {
        "key": "shift+cmd+x",
        "command": "workbench.action.tasks.terminate"
    },
    // Show Source Control
    {
        "key": "shift+cmd+g",
        "command": "workbench.view.scm"
    },
    {
        "key": "ctrl+shift+g",
        "command": "-workbench.view.scm"
    },
    // Lint
    {
        "key": "ctrl+shift+l",
        "command": "workbench.action.tasks.runTask",
        "args": "lint"
    },
    // Tests
    {
        "key": "shift+cmd+t",
        "command": "workbench.action.tasks.test"
    },
    // E2E Tests
    {
        "key": "ctrl+shift+e",
        "command": "workbench.action.tasks.runTask",
        "args": "e2e"
    },
    // Serve app
    {
        "key": "shift+cmd+s",
        "command": "workbench.action.tasks.runTask",
        "args": "serve"
    },
    // Open app
    {
        "key": "shift+cmd+o",
        "command": "workbench.action.tasks.runTask",
        "args": "open"
    },
    // Go to symbol (remapped)
    {
        "key": "ctrl+shift+o",
        "command": "workbench.action.gotoSymbol"
    }
]

Now you can lint, build, serve, open and test your Angular app simply by pressing a few keyboard shortcuts.  Each task will execute in its own terminal window, so you can run them simulateously.  Here is a demo of pressing Shift+Cmd+S to execute “ng serve” via “npm start.”

ng-serve-all.gif

Here is a demo of pressing Shift+Cmd+T to execute “ng test” via “npm run test.”

ng-test-all

Extensions

The first extension I’ll recommend for Angular development with VS Code is a file icon theme called vs-code-icons.  Its bright colors make working with the Explorer pane a pleasure.

There are certain chores that are tedious and error-prone.  One of those is adding and managing imports.  While VS Code can add imports via the “quick fix” (a.k.a. light bulb) menu, it will use double quotes, which will have to be changed later to single quotes (in compliance with the default Typescript linting rules), and it won’t care about the order of the import statements, or clean up imports that are no longer used.

ts-hero.png

The best way out of this quagmire is to install a VC Code extension called TypeScript Hero.  When you install it, you’ll get commands on the quick fix menu for adding imports, as well as keyboard shorcuts for managing imports.  My favorite is Ctrl+Opt+O, which organizes imports by sorting them according to convention and removing imports that are unused.  You can see which commands and shortcuts are available by opening the Command Palette (Shift+Cmd+P) and entering “TS Hero.”  All the commands are documented on the project home page.

Another thing that can mess you up is when you have to rename or move files and folders, which breaks imports for those files.  This is where an extension called Move TS can be a life saver.  To use it simply right-click on a file or folder in the Explorer pane and select “Move TypeScript.”

move-ts.gif

Two more essential extensions are TSLint, which provides real-time linting of TypeScript files in VS Code, and the Angular Language Service, which give you things like intellisense and Cmd+click go-to-definition inside of HTML template files.

ng-lang.gif

There are a few other miscellaneous extensions that I like.  If you’re editing markdown a lot, I recommend Markdown Shortcuts, which lets you create a code block by pressing Ctrl+M, Ctrl+C.  You also get an icon to open markdown preview to the side.

md-preview-before

Before

md-preview-after

After

For enhanced version control in the Editor, I recommend the Git History extension for comparing different file version in the git log, and Open in GitHub for jumping to a file in a remote repository — Bitbucket, Gitlab and VisualStudio.com are also supported.

git-history.gif

Chrome Debugging

Last but not least, one of the killer features of VS Code is support for debugging, and the Debugger for Chrome extension lets you set breakpoints and step through code in an Angular app without having to leave the comfort of the VS Code editor.  Start by adding the following configurations to your launch.json file.

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "launch",
            "name": "ng serve",
            "url": "http://localhost:4200/#",
            "webRoot": "${workspaceRoot}"
        },
        {
            "type": "chrome",
            "request": "launch",
            "name": "ng test",
            "url": "http://localhost:9876/debug.html",
            "webRoot": "${workspaceRoot}"
        }
    ]
}

Select the “ng serve” debug config, press Shift+Cmd+S to serve the app, then press F5 to hit your breakpoint.

ng-serve-debug.png

To debug unit tests, start the tests be pressing Shift+Cmd+T, select the “ng test” debug config, and press F5.  You should hit your breakpoint and be able to add watches, view the call stack, and step through your code.

ng-test-debug.png

I wrote a VS Code recipe for Angular CLI debugging, which contains further information, including installation instructions, gotchas and workarounds.

It’s a great time to be an Angular developer, and VS Code can help you be more productive — if you know how to bend it to your will.  Enjoy!

Posted in Technical | Tagged , | 3 Comments

Scaffold an Express Front End with Angular CLI

In my last post I explained how to build scalable Web API’s with Express and Async/Await.  The next chapter in the story is to build a cross-platform front end that consumes the back end Web API.  While there are a number of cross-platform UI technologies to choose from, such as Xamarin (C#), Swing (Java) or Kivy (Python), Angular (JavaScript) has garnered attention because it crosses the chasm that has separated web, desktop and mobile platforms, and it’s designed to be used with TypeScript, which increases developer productivity by adding static types and incorporating features from current and future versions of JavaScript.

Clone, fork or download the code for this post: https://github.com/tonysneed/Demo.Express.Angular-CLI

angular2-logo

Starting from Scratch

If you’re getting up to speed with Angular, you’ll want to check out the quickstart guide and source code, which describe how to build an Angular application from scratch.  To set up an Angular Hello World app, you’ll need to perform the following steps:

  1. Initialize package.json
  2. Install Angular packages
  3. Set TypeScript options with tsconfig.json
  4. Configure SystemJS with systemjs.config.js
  5. Add index.html host web page
  6. Create root app module
  7. Create root app component
  8. Create root app template
  9. Add main.ts platform bootstrapper

Whew! That’s a lot to go through to get started with a basic Angular application — and you haven’t even added your first component!

Introducing Angular CLI

To keep you from having to perform all these steps manually, Angular has created a tool called Angular CLI (in beta at the time of this writing), which you can use to scaffold new Angular projects, as well as add components to existing projects.

angular-cli

After installing Node.js, install angular-cli globally using NPM (Node Package Manager). While the documentation points you in the direction of creating a new app with Angular CLI, what I’d like to illustrate in this blog post is using Angular CLI to add a web front end to an existing project that includes a Web API built using Node with Express.

Begin with Express

For this purpose, I have created a starter branch in the code repository for this post. If you clone the repo, you can easily switch to the starter branch using GitHub Desktop.

ng-demo-starter.png

Begin by launching Visual Studio Code by opening a terminal at the repo location and entering  code. (you can use another code editor if you prefer).  Press Cmd + ` to open the integrated terminal in VS Code and enter npm install to restore package dependencies.  In the README.md file you’ll find instructions for building and running the Web API, namely, press Cmd + P and enter task serve-api.

Open a browser to localhost:/3000/api/products, and you should see JSON for several products.  Then terminate the running task by pressing Cmd + Shift + P, entering Terminate and selecting the first item.  (You may want to create a keyboard shortcut to terminate running tasks.)

products-json.png

Get Productive

Now that you have a functioning Web API using Express, it’s time to add a front end using Angular-CLI.  Open a terminal at the project root and enter ng init.  You’ll be prompted to overwrite a few files.  Enter Y to overwrite each of the files.  Don’t worry, you’ll use VS Code’s Git integration to see file differences and restore the original contents.

ng-init-overwrite-png.png

Back in VS Code, click on the Git icon to view file changes.  To restore the original content to overwritten files, simply select the changed file and copy content from the left to the right side.  You don’t need to change tslint.json, and the only change to .gitignore will be to add the dist folder.  But you’ll need to copy dependencies from the original to the new package.json file.

ng-init-package-diff.png

After updating both dev and runtime dependencies, run npm install to add packages required by Angular CLI.  Then go back to the terminal and enter npm start, which in turn will run ng serve to start a web server with browser live reload.  Open a browser to localhost:4200/, and you’ll see the root app component hosted in index.html.

ng-new-browser.png

To prove that live reload is working, you can open app.component.ts in VS Code and change the title property to 'Hello Angular CLI!'.  A few seconds later, you’ll see the change reflected in the browser without you having to refresh the page.  To stop the web server, go back to the terminal and press Ctrl + C.

Add Some Style

While Angular CLI created a functioning Angular app in very little time, the look is rather plain.  To spice things up, you’ll want to add some styling with a framework such as Angular Material or Bootstrap.  Because we’re building a straightforward app for this demo, Bootstrap will do fine.

For instructions on adding Bootstap with an app generated by Angular CLI, see the section called Global Library Installation in the ReadMe for the Angular CLI project on GitHub, as well as the ReadMe for ng2-boostrap.  Start by running:

npm install ng2-bootstrap bootstrap jquery --save

Open angular-cli.json and insert a new entry into the styles array.

"styles": [
        "styles.css",
        "../node_modules/bootstrap/dist/css/bootstrap.min.css"
],

Next insert two entries into the scripts array.

"scripts": [
  "../node_modules/jquery/dist/jquery.js",
  "../node_modules/bootstrap/dist/js/bootstrap.js"
],

Now you can apply Boostrap styles to HTML in your application.  Start by placing a <div> around the <h1> tag in app.component.html and setting class to "container".

<div class="container">
  <h1>
    {{title}}
  </h1>
</div>

This will apply the container bootstrap style to the root app component.  You may also wish to apply a different style to elements in the app component. You can accomplish this by setting styles in app.component.css.

h1 {
  color: #369;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 250%;
}

With these changes in place, start the app by running npm start and browse to the home page. You should see a more attractive user interface.

add-bootstrap.png

Your First Component

Now it’s time to scaffold your first component — how exciting!  Once again, Angular CLI will lend a helping hand.  To find out what it can do for you, have a look at the Angular CLI Reference document, which lists top-level commands.  Or you can enter ng --help at the command line, and then drill into a command by appending the name of the command you want to explore.  For example, to find out about the generate command enter: ng --help generate.  From the output you’ll see items you can generate (called “blueprints”).

Let’s start by generating a product class.  Since you may wish to share it among various components, you create  shared/models directories beneath app, then run:

ng generate class shared/models/product

This will generate a Product class in the shared/models folder, but it doesn’t have any properties. You can simply add a constructor with public parameters for each property you’d like to add.

export class Product {
  constructor(
    public productId: number,
    public productName: string,
    public unitPrice: number) { }
}

Now that you have a model, the next step is to generate a component, which you can produce by running ng generate with the component blueprint.  If you’re tired of typing generate every time, you can substitute it with the alias g.

ng g component products

This will create a ProductsComponent class along with an HTML template, a CSS file and a spec file for testing the component, and place them all in a products folder under app.

Angular is a framework for building SPA’s (Single Page Applications) and implements an MVVM (Model-View-ViewModel) architecture, with components playing the role of the ViewModel and templates serving as the View.  The way you associate components and templates is by applying an @Component decorator to a component and include a templateUrl.

Open products.component.ts and import Product, then add a title property set to ‘Products’ and a products property that is an array of Product.  Later you’re going to set products by retrieving products from the Express Web API in your project, but for now you can simply initialize products in the ngOnInit function to some test products.  You should also include an error property in the unlikely event something goes wrong. 🙂

import { Component, OnInit } from '@angular/core';
import { Product } from '../shared/models/product';

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {

  title: string = 'Products';
  products: Product[];
  error: any;

  constructor() { }

  ngOnInit() {
    this.products = [
            new Product(1, 'Product 1', 10),
            new Product(2, 'Product 2', 20),
        ];
  }
}

Now that you have a component for products, you can create a template by opening products.component.html and replacing generated content with HTML that contains bindings to properties in the component. Angular uses double curly braces for what is called interpolation to evaluate an expression that can refer to component properties.  You can also use built-in directives, such as *ngIf and *ngFor to perform data binding in the template.  For the products template, we’ll insert an HTML table with bootstrap styling that lists the properties of each product. (Note in the following HTML snippet, you’ll need to substitute left and right brackets and ampersands within the pre tags.)

<div class='panel panel-primary'>
    <div class='panel-heading'>
        {{title}}
    </div>
    <div class='panel-body'>
        <div class='table-responsive'>
            <pre>&lt;table class='table' *ngIf="products &amp;&amp; products.length"&gt;</pre>
                <thead>
                    <tr>
                        <th>Product Name</th>
                        <th>Unit Price</th>
                    </tr>
                </thead>
                <tbody>
                    <pre>&lt;tr *ngFor="let product of products"&gt;</pre>
                        <td>{{ product.productId }}</td>
                        <td>{{ product.productName }}</td>
                        <td>{{ product.unitPrice }}</td>
                    </tr>
                </tbody>
            </table>
        </div>
        <pre>&lt;div class="alert alert-danger" *ngIf="error"&gt;</pre>
            <strong>Error!</strong> {{error}}
        </div>
    </div>
</div>

Lastly, you’ll need to edit app.component.html to include the 'app-products' selector from products.component.ts.

<div class="container">
  <h1>
    {{title}}
  </h1>
  <pre>&lt;app-products&gt;&lt;/app-products&gt;</pre>
</div>

Run npm start from a command prompt and navigate to http://localhost:4200/. You should see the products component displayed in the browser.

products-browser.png

Connect to the Web API

While we have a products component that works, it does not yet display products retrieved from the back end Express Web API.  To accomplish this, we’ll use Angular CLI to generate a products service.

ng g service shared/services/products

Add a private parameter to the constructor to inject the Angular Http client into the products service, then add a getProducts function that calls this._http.get and converts it to a promise, so that you can handle success by calling .then, or failure by calling .catch. Here is the code for the ProductsService class.

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';

import { Product } from '../models/product';
import { Urls } from '../constants';

@Injectable()
export class ProductsService {

  private _productsUrl = Urls.BaseUrl + 'api/products';

  constructor(private _http: Http) { }

  getProducts(): Promise {
    return this._http.get(this._productsUrl)
      .toPromise()
      .then(resp => resp.json() as Product[])
      .catch(this.handleError);
  }

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

In order to use ProductsService, you’ll need to add it as a provider to @NgModule in app.module.ts, after importing the ProductsService module.  Lastly, you’ll need to refactor ProductsComponent to use ProductsService, by adding a private parameter to the constructor, then calling getProducts to set the products property if successful or the error property if unsuccessful.

import { Component, OnInit } from '@angular/core';
import { Product } from '../shared/models/product';
import { ProductsService } from '../shared/services/products.service';

@Component({
  selector: 'app-products',
  templateUrl: './products.component.html',
  styleUrls: ['./products.component.css']
})
export class ProductsComponent implements OnInit {

  title: string = 'Products';
  products: Product[];
  error: any;

  constructor(private _productService: ProductsService) { }

  ngOnInit() {
    // Replaced with products service
    // this.products = [
    //         new Product(1, 'Product 1', 10),
    //         new Product(2, 'Product 2', 20),
    //     ];

    this._productService.getProducts()
      .then(products => this.products = products)
      .catch(error => this.error = error);
  }
}

See It in Action

It’s finally time to run the front and back ends of your Angular-Express application at the same time. But for it to succeed, you’ll need to add a proxy to the back end so that calls to the Web API on port 4200 will be sent to port 3000 instead.  Add a file to the project root called proxy.conf.json with the following content:

{
  "/api": {
    "target": "http://localhost:3000",
    "secure": false
  }
}

Then update the start script in package.json to append a proxy-config parameter.

"start": "ng serve --proxy-config proxy.conf.json"

With the back end proxy configured, you can start the Express Web API by running the serve-api gulp task.  Then you can start the Angular app by running npm start.  You should now see a list of products that have been retrieved from the Web API.

products-final-browser.png

Clone, fork or download the code for this post: https://github.com/tonysneed/Demo.Express.Angular-CLI

Congratulations!  You have used Angular CLI to scaffold a front end to an Express Web API back end.  Because the front end is independent of the back end, there’s nothing to stop you from swapping out the Express implementation for another technology stack, such as ASP.NET Core, without affecting the Angular front end.

Posted in Technical | Tagged , , | 10 Comments

Scalable Web API’s with Express and Async-Await

If you’re a C# developer, you’ve most likely fallen in love with async / await. It makes writing asynchronous code as natural as writing non-asynchronous code, resulting in code that is easier to read, debug and maintain.

The exciting news is that async / await goodness has found its way into the world of JavaScript!

async-await-js.png

Currently async / await is a proposed feature of ECMAScript 2017 (ES8) and is moving from stage 3 (candidate) to stage 4 (finished) in the TC39 process.  But there’s no need to wait for the release of ES2017 — with the help of JavaScript transpilers, such as Babel and Typescript, which can translate future versions of JavaScript into downlevel versions compatible with most browsers, you can start using async / await today!

Clone, fork or download the code for this post: https://github.com/tonysneed/Demo.Express.Async

Because of features like type-safety, interfaces and generics, I’m an unabashed TypeScript fanboy, so I’m going to talk about how to use async / await with TypeScript.  But the approach is the same using the async functions Babel plugin.

TypeScript has had support for async / await on the server-side for Node with ES2015 since version 1.7, but it will add support for async / await on the client-side for ES5/ES3 with version 2.1.  In this blog post, I’ll focus on using async / await to create RESTful Web API’s with Express, a minimalist web framework for Node.

The primary motivation for async / await on the server-side is scalability with Web API’s that perform asynchronous I/O operations.  So if you are performing database queries, calling other web services or interacting with the file system, then it’s imperative that you not block the executing thread waiting for I/O operations to complete.  As I demonstrated in a prior post on async functions in C#, blocking threads on a server will result in creating additional threads, each of which carries a great deal of overhead, both in terms of memory and CPU utilization, which can impact application performance.

From CallbackHell to Async-Await Heaven

In the JavaScript world, you’re less likely to use a synchronous API in an Express application, because just about everything is done with callbacks.  The problem is that you can easily find yourself slipping into what is sometimes referred to as callback hell.  Here is a brief example:

try {
    greeter.getGreeting(g => {
        console.log(g);
        try {
            greeter.getGreeting(g => {
                console.log(g);
                try {
                    greeter.getGreeting(g => {
                        console.log(g);
                    });
                } catch (error) {
                    console.log(error.message);
                }
            });
        } catch (error) {
            console.log(error.message);
        }
    });
} catch (error) {
    console.log(error.message);
}

Promises were invented to help prevent callback hell. Using the getGreeting function as an example, you can refactor it to return a promise, in which you call resolve to return a result or reject to throw an error.

getGreeting(): Promise<any> {
    return new Promise((resolve, reject) => {
        try {
            let greeting = generateGreeting();
        } catch (error) {
            reject(error);
        }
        resolve("Hello");
    });
}

The caller of getGreeting can then handle success and failure by calling then or catch on the returned promise, and catch will handle an error from any of the preceeding then handlers.

greeter.getGreeting()
    .then(g => {
        console.log(g);
        return greeter.getGreetingAsync();
    })
    .then(g => {
        console.log(g);
        return greeter.getGreetingAsync();
    })
    .then(g => {
        console.log(g);
    })
    .catch(e => {
        console.log(e);
    });

While an improvement over callback hell, promises still require async code that is quite different than synchronous code.  To help close the gap, async / await makes it possible to write asynchronous code in much the same way as synchronous code.

async function main(): Promise<void> {
    try {
        let g1 = await greeter.getGreeting();
        console.log(g1);
        let g2 = await greeter.getGreeting();
        console.log(g2);
        let g3 = await greeter.getGreeting();
        console.log(g3);
    }
    catch (error) {
        console.log(error);
    }
}

Each call to getGreeting will take place sequentially without any blocking, and a single catch block will handle errors from any awaited method.

Async-Await with Express

When building a Web API that performs I/O against a data store, it’s helpful to use a repository pattern, so that you can mock the repository without external dependencies, as well as swap out one data access API for another should the need arise.  In the code for this post, I created a ProductsRepository class that uses an in-memory collection to simulate a persistent data store.  It imports a Product class with properties for productId, productName and unitPrice.

class Product {
    constructor(
        public productId: number,
        public productName: string,
        public unitPrice: number) { }
}

export { Product };

ProductsRepository has promise-based methods for retrieveAll, retrieve, create, update and delete, which call resolve if successful or reject if the id is not valid.

import { Product } from "../models/product";

// Methods return promises to simulate IO-bound operations

export default class ProductsRepository {

    // Array of products
    private _products = [
        new Product(1, "Chai", 10),
        new Product(2, "Espresso", 20),
        new Product(3, "Capuccino", 30),
        new Product(4, "Macchiato", 40),
        new Product(5, "Americano", 50),
        new Product(6, "Flat White", 60),
    ];

    retrieveAll(): Promise<Product[]> {
        return new Promise((resolve, reject) => {
            resolve(this._products);
        });
    }

    retrieve(id: number): Promise<Product> {
        return new Promise((resolve, reject) => {
            let product = this.getProduct(id);
            if (product === null) {
                reject(`Invalid id: ${id}`);
            }
            resolve(product);
        });
    }

    create(product: Product): Promise<Product> {
        return new Promise((resolve, reject) => {
            if (this.getProduct(product.productId) !== null) {
                reject(`Product exists with id: ${product.productId}`);
            }
            this._products.push(product);
            resolve(product);
        });
    }

    update(product: Product): Promise<Product> {
        return new Promise((resolve, reject) => {
            let existingProduct = this.getProduct(product.productId);
            if (existingProduct === null) {
                reject(`Invalid id: ${product.productId}`);
            }
            let index = this._products.indexOf(existingProduct);
            this._products[index] = product;
            resolve(product);
        });
    }

    delete(id: number): Promise<void> {
        return new Promise((resolve, reject) => {
            if (this.getProduct(id) === null) {
                reject(`Invalid id: ${id}`);
            }
            this._products.splice(id - 1, 1);
            resolve();
        });
    }

    private getProduct(id: number): Product | null {
        let products: Product[] = this._products
            .filter(p => p.productId === id);
        if (products.length > 0) {
            return products[0];
        }
        return null;
    }
}

In Express it’s customary to create a router to handle requests for a path segment, such as api/products.  The idea is similar to controllers in ASP.NET Web API and promotes improved modularity.  Here is a router that calls promise-returning functions on the products repository to perform I/O operations asynchronously.  But rather than resort to Promise’s then and catch methods, we use async / await so that the code looks cleaner and is easier to undersand, including the use of try / catch for error handling.

import * as express from "express";
import { Request, Response } from "express";

import { Product } from "../models/product";
import ProductsRepository from "../services/products-repo";

let router = express.Router();
let productsRepo = new ProductsRepository();

// GET route
router.get("/", async (req: Request, resp: Response) => {
    console.log("Retrieving products");
    try {
        let products = await productsRepo.retrieveAll();
        resp.json(products);
    } catch (error) {
        console.log(error);
        resp.sendStatus(500);
    }
});

// GET route with id
router.get("/:id", async (req: Request, resp: Response) => {
    console.log(`Retrieving product id ${req.params.id}`);
    try {
        let product = await productsRepo.retrieve(+req.params.id);
        resp.json(product);
    } catch (error) {
        console.log(error);
        if (error.indexOf("Invalid id") > -1) {
            resp.sendStatus(404);
            return;
        }
        resp.sendStatus(500);
    }
});

// POST route
router.post("/", async (req: Request, resp: Response) => {
    console.log(`Creating product: ${JSON.stringify(req.body)}`);
    try {
        let product = await productsRepo.create(req.body);
        resp.json(product);
    } catch (error) {
        console.log(error);
        if (error.indexOf("Product exists") > -1) {
            resp.sendStatus(400);
            return;
        }
        resp.sendStatus(500);
    }
});

// PUT route
router.put("/", async (req: Request, resp: Response) => {
    console.log(`Updating product id ${req.body.productId} to: ${JSON.stringify(req.body)}`);
    try {
        let product = await productsRepo.update(req.body);
        resp.json(product);
    } catch (error) {
        console.log(error);
        if (error.indexOf("Invalid id") > -1) {
            resp.sendStatus(404);
            return;
        }
        resp.sendStatus(500);
    }
});

// DELETE route with id
router.delete("/:id", async (req: Request, resp: Response) => {
    console.log(`Deleting product id ${req.params.id}`);
    try {
        await productsRepo.delete(+req.params.id);
        resp.end();
    } catch (error) {
        console.log(error);
        if (error.indexOf("Invalid id") > -1) {
            resp.sendStatus(404);
            return;
        }
        resp.sendStatus(500);
    }
});

// Export products router module
export { router as productsRouter };

To use async / await with Node, you’ll need a tsconfig.json file at the root of your Web API that specifies a target of es2015. Then add a server.ts file to bootstrap Express and plug the products router into the pipeline.  You can smoke test your Web API using a tool such as Fiddler or Postman.

Enjoy!

Posted in Technical | Tagged , | Leave a comment

Now is a Great Time to Learn TypeScript

Today I am pleased to share some exciting news.  I just finished authoring a new 5-day course on TypeScript!

Essential TypeScript 2.0 with Visual Studio Code

It is timed to coincide with the 2.0 release of TypeScript, a relatively new programming language designed to make JavaScript strongly typed and more capable of supporting large-scale web applications.

gk-dm.png

I authored the course for Global Knowledge, an IT training company that in 2015 acquired DevelopMentor, a developer training company where I authored and taught courses in C# and .NET since 2006.  The course represents the culmination of a four-month odyssey, in which I not only had to grok TypeScript grammar and syntax but also mastered an entirely new technology stack and toolchain.

It was fun!

Here is a list of topics included in the course:

  1. Introduction to TypeScript
  2. Language Basics
  3. Using Visual Studio Code with TypeScript
  4. Task Automation, Unit Testing, Continuous Integration
  5. The TypeScript Type System
  6. Functional Programming
  7. Asynchronous Programming
  8. Object-Oriented Programming
  9. Generics and Decorators
  10. Namespaces and Modules
  11. Practical TypeScript with Express and Angular

While performing the herculean task of creating a 5-day class with slides, labs and numerous demos, I have to say I thoroughly enjoyed the process of adding a new weapon to my arsenal as a software developer and the chance to venture off in an entirely new direction.  So I thought I would take this opportunity to put together some thoughts about why I think this is a great time to learn web development with TypeScript — and at the same time plug my new course. 🙂

Revenge of JavaScript

One motivation for me to pick up JavaScript (of which TypeScript is a superset) is that we no longer live in a world in which Windows is dominant, and JavaScript can be used to write apps for more than just web browsers — you can use it to write desktop and mobile applications, as well as backend services running in the Cloud.  It has unwittingly become one language to rule them all.

express-logo.jpg

Secondly, web development has matured to the point where it is possible to write an app that has nearly the same interactivity and responsiveness as a traditional desktop application.  One of the reasons I previously gravitated toward WPF (and unconsciously drifted away from ASP.NET) was that desktop and native mobile apps seemed to offer a more fluid user experience where you could work with data locally without having to continually refresh the screen from a remote server.

All that has changed with the advent of SPA’s (Single Page Applications), where turbocharged JavaScript engines quickly render rich, interactive web pages.  It’s the perfect time to build SPA’s because second generation frameworks have emerged that take web development to a whole new level and implement the MVVM (Model-View-ViewModel) pattern (or some MV-* variation), providing benefits such as better separation of concerns, testability and maintainability.  Frameworks like Angular, Aurelia and ReactRedux also provide tools for quickly scaffolding new applications and preparing them for production.

angular2-logo

TypeScript has emerged as the language of choice for building many of these kinds of modern web apps, because its strong-typing enables features we’ve come to take for granted, such as interfaces and generics. It also provides things most developers couldn’t think of doing without, such as intellisense, statement completion code refactorings.  And TypeScript’s popularity received a shot in the arm from the Angular team at Google, which is using it to build Angular.  In addition, most of their tutorials are written in TypeScript.

JavaScript Has Grown Up

In 2015 JavaScript had its most significant upgrade since it was created in 1995 by Brendan Eich in a 10-day hackathon.  With the release of ECMAScript 2015, JavaScript received a slew of new features, many of which classical languages, such as Java and C#, have had for years.  These include classes, inheritance, constants, iterators, modules and promises, to name a few.  TypeScript not only includes all ES 2015 features, but it fast-forwards to future versions ECMAScript by supporting proposed features such as async and await operators, which help simplify asynchronous code.  TypeScript lets you use advanced features of JavaScript by transpiling down to ES5, a flavor of JavaScript compatible with most browsers.

es2015

When you put modern JavaScript together with TypeScript, you get a powerful combination that gives you just about everything you might want for building SOLID applications that can run in the browser, on the server or on mobile and desktop platforms.

Shiny New Tools

The nice thing about TypeScript is that you’re free to use whatever tool you like, from a full-fledged IDE like Visual Studio or Web Storm, to a lightweight code editor, such as SublimeText, Atom, Brackets or Visual Studio Code.  While there’s nothing wrong with any of these options, I prefer using VS Code for TypeScript development, because it comes with TypeScript in the box and the team eats their own dogfood by using TypeScript to build the editor.

vs-code

Coming from a C# background, where I was confined to using Visual Studio on Windows with its enormous overhead, I can run code editors such as VS Code on my Mac, giving me one less reason to fire up a Windows VM.  VS Code starts quickly, and I can open it at a specific folder from either the Finder or Terminal.  I also found navigation in VS Code to be straightforward and intuitive, and you can perform many tasks from the command palette, including custom gulp tasks.  VS Code functions as a great markdown editor with a side-by-side preview that refreshes in real time as you make changes.  It has Git integration and debugging support, as well as a marketplace of third-party extensions that provide a variety of nifty services, such as TypeScript linting and Angular 2 code snippets.  Put it all together, and VS Code is a perfect fit for TypeScript development.

Living in Harmony

One of the most compelling reasons I can think of for picking up TypeScript is that it’s the brainchild of the same person who created C#, Anders Hejlsberg, who in a prior life also invented Turbo Paschal and Delphi.  Having such an amazing track record, I have a high degree of confidence in following him into the world of web and native JavaScript development, where he has made it possible to be more productive and write code that is more resilient because the TypeScript compiler is able to catch problems at development-time that would otherwise only become apparent at runtime.

Lastly, it’s significant that Anders did not choose to create a language that is not JavaScript, such as CoffeeScript, but rather one that includes all of JavaScript with optional type annotations that disappear when TypeScript is compiled down to plain old JavaScript.  In fact, all JavaScript is valid TypeScript, and you can put in annotations or leave them out wherever you like, giving you the best of both dynamic and static typing.  In other words, TypeScript does not impose itself on you or dictate that you follow any of its prescriptions.

To conclude, this is a great time for learning TypeScript, even if you don’t know much about JavaScript, because the two are mostly one and the same, except for extra features designed to make your life easier and that allow you to write JavaScript programs capable of growing over time without becoming too unwieldy.  This frees you to concentrate on learning JavaScript frameworks, such as Express and Angular, that empower you to build RESTful web services and modern client applications.

Happy coding!

Posted in Technical | Tagged | 2 Comments

Simple MVVM Toolkit – It Lives!

Update: Version 1.0 of Simple MVVM Toolkit Express has now been released and is based on version 1.0 RTM of .NET Core: https://www.nuget.org/packages/SimpleMvvmToolkit.Express. Source code and samples can be found here: https://github.com/SimpleMvvm

Now that .NET Core is stable and RC2 has been released, and the .NET Platform Standard has been proposed to replace Portable Class Libraries, I thought it would be a good idea to port my Simple MVVM Toolkit to .NET Core and provide support for additional platforms, such as Universal Windows Platform and the latest version of Xamarin for cross-platform mobile apps, included iOS from Apple and Android from Google.

dotnet-core.png

Rather than update my existing repository, I decided it was time for a fresh start.  So I createed a new project on GitHub called Simple MVVM Toolkit Express.  It is compatible with the following platforms:

  • Portable Class Libraries: Profile 111 – .NET 4.5, AspNet Core 1.0, Windows 8, Windows Phone 8.1
  • .NET Framework 4.6
  • Universal Windows Platform 10.0
  • Mono/Xamarin: MonoAndroid60, XamariniOS10
  • .NET Core 1.0: NetStandard 1.3

I decided to break compatibility with the following legacy frameworks: .NET 4.0 and Silverlight.

The toolkit has all the major features of the classic version, including classes for models and view models, support for validation and editing with rollbacks, as well as a leak-proof message bus (aka mediator or event aggregator).  Platform-specific threading implementations have been removed, because it’s better to use C#’s built-in async support.

I published a pre-release NuGet package, which you can find here: https://www.nuget.org/packages/SimpleMvvmToolkit.Express.  And I’ve created samples for WPF, UWP and Xamarin, which you can find on the SimpleMvvm home repository.

I used the dotnet CLI (command-line interface) tool chain to build the project and generate a multi-targeted NuGet package, but I had to modify the generated nuspec file to work around some compatibility issues.  In the end, it was a great learning experience, and I found it reassuring that I could continue to use a popular framework for building many different kinds of client applications using the Model-View-ViewModel design pattern.

Happy coding!

Posted in Technical | 14 Comments

Getting Visual Studio Code Ready for TypeScript: Part 3

Part 3: Injecting Scripts with Gulp

This is the third part in a series of blog posts on Getting Visual Studio Code Ready for TypeScript:

  1. Compiling TypeScript to JavaScript
  2. Writing Jasmine Tests in TypeScript
  3. Injecting Scripts with Gulp (this post)

Leveraging Gulp

In the first and second post in this series I showed how you can use Gulp to automate common tasks such as compiling TypeScript to JavaScript and running Jasmine tests in a browser.  While Gulp is not strictly necessary to perform these tasks, it allows you to chain together multiple tasks, which can give you a smoother workflow.

gulp-partial

You can download a sample project with code for this blog post.  You can also download my Yeoman generator for scaffolding new TypeScript projects for Visual Studio Code.

For example, we defined a “watch” task with a dependency on the “compile” task, so that Gulp performs a compilation before watching for changes in any TypeScript files.  When changes are detected, the “compile” task is then re-executed.

gulp.task('compile', function () {

    exec('rm -rf dist && tsc -p src');
});

gulp.task('watch', ['compile'], function () {

    return gulp.watch('./src/**/*.ts', ['compile']);
});

Likewise, we defined a “test” task with a dependency on the “watch” task, so that changes to any TypeScript files will cause browser-sync to reload the browser when it detects that the JavaScript files have been re-generated.

gulp.task('test', ['watch'], function () {

    var options = {
        port: 3000,
        server: './',
        files: ['./dist/**/*.js',
                './dist/**/*.spec.js',
                '!./dist/**/*.js.map'],
        // Remaining options elided for clarity
    };

    browserSync(options);
});

Listing Tasks

While VS Code allows you to execute gulp tasks from within the editor, you may sometimes prefer to use Gulp from the Terminal (if for no other reason than to see all the pretty colors).  To make this easier, we can use a plugin that will list all the tasks we’ve defined in our gulpfile.js.  But before we get into that, we can make our lives easier by using a plugin called gulp-load-plugins, which will relieve us from having to define a separate variable for each plugin we wish to use.  All we need to do is define a $ variable, then use it to execute other gulp plugins we’ve installed.

var $ = require('gulp-load-plugins')({ lazy: true });

To list tasks in gulpfile.js, we can define a “help” task which uses the gulp-task-listing plugin to list all of our tasks.  We’ll follow a convention which uses a colon in the task name to designate it as a sub-task.  We can also define a “default” task which calls the “help” task when a user enters “gulp” in the Terminal with no parameters.

gulp.task('help', $.taskListing.withFilters(/:/));
gulp.task('default', ['help']);

You’ll need to install both Gulp plugins using npm.

npm install --save-dev gulp-load-plugins gulp-task-listing

Then open the Terminal, type “gulp” (no quotes) and press Enter.  You should see a list of tasks displayed.  To execute a task, simply type “gulp” followed by a space and the name of the task.

gulp-help

Injecting Scripts

In my last blog post I described how you can run Jasmine tests in a browser by serving up an HTML file which included both source and spec JavaScript files.  But this required you to manually insert script tags into SpecRunner.html.  You might have asked yourself if there might be a way to inject scripts into the spec runner automatically whenever you executed the “test” task. Well it just so happens: there’s plugin for that!™ It’s appropriately called gulp-inject, and you can add an injectScripts function to gulpfile.js which will inject scripts into SpecRunner.html based on globs for source and spec files.

var inject = require('gulp-inject');

function injectScripts(src, label) {

    var options = { read: false, addRootSlash: false };
    if (label) {
        options.name = 'inject:' + label;
    }
    return $.inject(gulp.src(src), options);
}

Now add a “specs:inject” gulp task which calls injectScripts to insert the source and spec scripts.  Because we only intend to call this task from other tasks, we can classify it as a sub-task by inserting a colon in the task name.

gulp.task('specs:inject', function () {

    var source = [
        './dist/**/*.js',
        '!./dist/**/*.js.map',
        '!./dist/**/*.spec.js'];

    var specs = ['./dist/**/*.spec.js'];

    return gulp
        .src('./specrunner.html')
        .pipe(injectScripts(source, ''))
        .pipe(injectScripts(specs, 'specs'))
        .pipe(gulp.dest('./'));
});

The gulp-inject plugin will insert selected scripts at each location, based on a comment corresponding to the specified label.  Simply edit SpecRunner.html to replace the hard-coded script tags with specially formatted comments. After running the “specs:inject” task, you should see the appropriate scripts inserted at these locations

<!-- inject:js -->
<!-- endinject -->

<!-- inject:specs:js -->
<!-- endinject -->

Injecting Imports

In addition to inserting source and spec scripts, you’ll also want to inject System.import statements into the spec runner so that system.js can provide browser support for module loading.  For that you’ll need to install packages for glob, path, gulp-rename, and gulp-inject-string, then add an injectImports function to gulpfile.js.

var glob = require('glob');
var path = require('path');

function injectImports(src, label) {

    var search = '/// inject:' + label;
    var first = '\n    System.import(\'';
    var last = '\'),';
    var specNames = [];

    src.forEach(function(pattern) {
        glob.sync(pattern)
            .forEach(function(file) {
                var fileName = path.basename(file, path.extname(file));
                var specName = path.join(path.dirname(file), fileName);
                specNames.push(first + specName + last);
            });
    });

    return $.injectString.after(search, specNames);
}

Then add an “imports:inject” task which calls injectImports to insert system imports into a file called system.imports.js.

gulp.task('imports:inject', function(){

    gulp.src('./util/system.template.js')
        .pipe(injectImports(['/.dist/**/*.spec.js'], 'import'))
        .pipe($.rename('./util/system.imports.js'))
        .pipe(gulp.dest('./'));
});

Modify SpecRunner.html to replace the script that uses System.import with a reference to system.imports.js.

<script src="util/system.imports.js"></script>

When you execute the “imports:inject” gulp task, it will search a file called system.template.js for a triple-dash comment with the text “inject:import”, where it will inject imports for each spec file. The result will be written to system.imports.js.

Promise.all([
    /// inject:import
    System.import('dist/greeter/greeter.spec'),
    System.import('dist/italiangreeter/italiangreeter.spec')
]);

Lastly, you need to update the “test” task in gulpfile.js to add the two sub-tasks for injecting scripts and imports. This will ensure they are executed each time you run your tests.

gulp.task('test', ['specs:inject', 'imports:inject', 'watch'], function ()

Debugging Gulp Tasks

If you run into problems with any gulp tasks, it would help if you could set breakpoints in gulpfile.js, launch a debugger and step through your code to see what went wrong.  You can do this in VS Code by adding an entry to the “configurations” section of your launch.json file, in which you invoke gulp.js and pass a task name.

{
    "name": "Debug Gulp Task",
    "type": "node",
    "request": "launch",
    "program": "${workspaceRoot}/node_modules/gulp/bin/gulp.js",
    "stopOnEntry": false,
    "args": [
        // Replace with name of gulp task to run
        "imports:inject"
    ],
    "cwd": "${workspaceRoot}"
}

If you set a breakpoint in the “imports:inject” task, select “Debug Gulp Task” from the drop down in the Debug view in VS Code and press F5, it will launch the debugger and stop at the breakpoint you set.  You can then press F10 (step over) or F11 (step into), view local variables and add watches.

gulp-debug

Learning Gulp

If you would like to learn more about Gulp, I highly recommend John Papa’s Pluralsight course on Gulp, where he explains how to use Gulp to perform various build automation tasks, such as bundling, minification, versioning and integration testing. While the learning curve may appear steep at first, Gulp will make your life easier in the long run by automating repetitive tasks and allowing you to chain them together for a streamlined development workflow.

Posted in Technical | Tagged , | Leave a comment