Aurelia TypeScript Samples

skel-nav-esri-vs-ts

This is a derrivative of Aurelia/Skeleton-Navigation, with the JavaScript code ported to TypeScript. The project uses an AMD bundle of the Aurelia repos, the ESRI / Dojo module loader and runs in Visual Studio 2015 (Free Community Edition or higher).

skel-nav-esri-vs-ts - visual studio (using esri dojo amd module loader) on GitHub

This sample includes an additional aurelia view & view model showing a very basic esri map.

Steps to get running:

  1. View running project
  2. Make sure you have Visual Studio 2015: Free Comunity Edition or higher

    TypeScript 1.5 is included in this install.

  3. Run Visual Studio 2015
  4. Open solution in skel-nav-esri-vs-ts
  5. Run solution using chrome, firefox, or IE

Some background on the sample:

For this sample, we'll choose to use an existing AMD bundle of the Aurelia libraries and the Dojo AMD loader, so we can easily include an ESRI map in our sample application.

Lets dive in and look at some code.

main.ts

The role of the main.ts file is to initialize Aurelia and then start up with app view and view model.

The first thing to notice, is that the TypeScript version of this code and the JavaScript version are identical.

export function configure(aurelia) {
  aurelia.use
    .standardConfiguration()
    .developmentLogging();

  aurelia.start().then(a => a.setRoot());
}

The reason this works, is because JavaScript IS TypeScript.

But now lets pretend that we didn't have the code to paste in from the Aurelia documentation. This is where TypeScript begins to shine.

Notice that the configure function accepts an Aurelia object as its parameter. At this point, no type has been specified, so the IDE tooling can't help us figure out how to use the object we've been passed.

To obtain the Aurelia type, we need to import it from the aurelia-framework repo. As I type the import statement, the IDE tooling suggests possible imports i might want:

TODO: screen shot of VS2015 import intellisense

Now I can fill in the actual type I want to pull in from the aurelia-framework repo. Again the IDE tooling leads the way, showing me the repo's exports, and helping me pick the one I'm seeking:

TODO: screen shot of VS2015 export intellisense

The fact that I'm picking the name from a list and not typing it out manually reduces typos. But lets say I'm feeling pretty good about my typing, and ignore the IDE tooling's helpful suggestion. If I do make a typo, a friendly, unobtrusive red squiggly underline is presented to me.

TODO: screen shot of import typo

So at this point I have successfully imported the Aurelia type, and can now use it to annotate the parameter type in the configure function as shown below. Type annotation allows the IDE tooling to help me discover and chose valid members of the object.

TODO: screen shot of Aurelia member intellisense

At this point you may be wondering how the IDE actually knew about the types in the aurelia-framework import. Let's talk about that next.

TypeScript Type Definition (.d.ts) Files

Each Aurelia repo has a corresponding .d.ts file that declares the type information exposed by that repo's public API. Our project's Aurelia .d.ts files can be found here.

You can inspect the aurelia-framework.d.ts file if you'd like a glimpse at the syntax that defined the Aurelia type we used in our example above.

But how did the IDE know which .d.ts files to include in our project? The next section explains that very mechanism.

The tsconfig.json Project File

The tsconfig.json file is a universal project format for TypeScript. Our project's tsconfig.json file is located here.

This is the tsconfig.json file in our project (with the files property stripped down to save space).

{
    "version": "1.5.1",
    "compilerOptions": {
        "target": "es5",
        "module": "amd",
        "declaration": false,
        "noImplicitAny": false,
        "removeComments": false,
        "noLib": true,
        "emitDecoratorMetadata": true
    },
    "filesGlob": [
        "./**/*.ts",
        "!./node_modules/**/*.ts"
    ],
    "files": [
        // ...
    ]
}

The files property is filled in automatically by Atom-TypeScript using the filesGlob property's patterns. This occurs when saving tsconfig.json.

Atom-TypeScript has an excellent description of the tsconfig.json details here

So the answer to the question about how Atom-TypeScript knows which files to include comes from (in our case) the expression: ./**/*.ts which says start in the root folder of the project and pull in any files ending in .ts (which includes files that end in .d.ts), so all of our .d.ts files in the typings folder will be included.

The final concept we'll explore before we wrap up (and provide you with a list related resources), is the topic of decorators

Decorators

From the excellent decorator description by Mr. Yehuda Katz:

Decorators make it possible to annotate and modify classes and properties at design time.

While ES5 object literals support arbitrary expressions in the value position, ES6 classes only support literal functions as values. Decorators restore the ability to run code at design time, while maintaining a declarative syntax.

A decorator is:

  • an expression
  • that evaluates to a function
  • that takes the target, name, and property descriptor as arguments and
  • optionally returns a property descriptor to install on the target object

Lets make that abstract concept more concrete by looking at some specific ways that Aurelia uses decorators.

First off, decorators are actually part of the ES7 (a.k.a ES2016) spec, but are available for your use today through the the TypeScript 1.5 Compiler (here's a TypeScript roadmap).

Now lets look at some code that shows how easy it is for you to use decorators.

Dependency Injection

We are going to look at a simple example of dependency injection. You may be very familiar with the concept of dependency injection, but if you're not, think about the flickr view and view model in our sample application. The view model needs to retrieve a collection of images from the flickr web services.

We are going to "separate our concerns" into separate responsibilities - our flickr view model will worry only about how to display the results that come back from the web service, and the Aurelia Http Client will be responsible for managing the http retrieval of data from the web service.

We really don't want our flickr view model to even worry about how to obtain the Aurelia Http Client. We just want to specify in our constructor that we need it and we'll let the framework take care of the rest.

All we have to do to get the Aurelia framework to "inject" the Aurelia Http Client into our constructor is to first import and then place the @autoinject decorator on the class.

Intellisense will help us discover and correctly type the text as before.

TODO: screen shot of auto inject intellisense in vs 2015

So you can see, we just ask for the Http Client and then we use its public API. In fact, our flickr view model will be happy with any object that provides the same (polymorphic) API - that is one of the very powerful aspects of dependency injection.

autoinject-decorator-2-1

Bindable Decorator

Before we talk about the bindable decorator, we'll need a little bit of background.

First, a custom element is a reusable view / view model pair that can be embedded in another view.

In our sample project, we have a custom element called nav-bar:

nav-bar

The nav-bar custom element gives us a great way to "encapsulate" the layout and functionality of our navigation menu in a single unit that can be included with a clean, understandable syntax:

nav-bar-binding-2

Notice in the code above that our custom element (the nav-bar) has a router property that we are able to set (bind) to the router in our main application. This works because the Aurelia framework supports bindable properties in custom elements. But how does the Aurelia framework know which properties are to be exposed as such? (hopefully you are thinking DECORATORS !!).

Within the nav-bar view model, the @bindable decorator is used to alert the Aurelia framework that we want the router property to be bindable.

bindable-decorator

So in the outer code that embeds the nav-bar, we bound it to the router, and now router is accessible to the nav-bar's view. Notice how clean and expressive the nav-bar's view template is, when referencing the router object's properties:

nav-bar-binding-1