In the latest version of ServiceStackVS, we've added an Aurelia template to help you get started with Aurelia, TypeScript, JSPM and Gulp.
Aurelia is a JavaScript client framework for mobile, desktop and web leveraging simple conventions and empowering creativity.
Aurelia's strong focus on simple un-obtrusive conventions to produce a great developer experience. ServiceStack shares this same focus on developer experience to enable developers to concentrate on what's important to their solution.
This template has a lot of similarities to the other ServiceStackVS templates but with all the configuration to help kick start your Typescript React application. NPM's package.json is setup to include all the parts you'll need including:
- Typings - For ambient TypeScript definitions
- JSPM - To manage your application dependencies and give you a smooth workflow
- Gulp - Grunt has been removed in favor of a Gulp only approach to simplify build setup.
Just like our other TypeScript/JSPM/Gulp templates, we use Gulp for staging and deploying (via MSDeploy) to allow for simple deployments to an existing IIS server. The following tasks are setup when using the template.
default
- This is the default task that builds and stages your application.00-update-deps-js
- This task is to regeneratedeps.lib.js
fromdeps.tsx
which is used to reduce how much HTTP requests are done each time you make a change.01-package-server
- build and stage the server components of your application.02-package-client
- build and stage the client side resources of your application.03-deploy-app
- This deploys your application usingmsdeploy
andconfig.json
found inwwwroot_build/publish
.package-and-deploy
- This task is the whole process of building, staging and deploying your application.
To try get the most of using Aurelia as your client application, this template is structured in a way that should grow well with your application as well as following some of the known patterns Aurelia encourages. Within the src
directory, the application is broken up into 3 major parts.
To start your Aurelia application, we are using the aurelia-bootstrapper
module. The usage of the bootstrapper is declared in the index.html
file.
<body aurelia-app="src/main">
...
<script>
System.import('aurelia-bootstrapper');
</script>
</body>
This will kick off our application by looking into src/main.ts
and specifically for the configure(aurelia: Aurelia)
function for out application initialization. The template is using the standardConfiguration
, the developmentLogging
and our own internal "Feature" for reusable common resources in the resources
directory.
import {Aurelia} from 'aurelia-framework';
export function configure(aurelia: Aurelia) {
aurelia.use
.standardConfiguration()
.feature('src/resources')
.developmentLogging();
aurelia.start().then(x => x.setRoot('src/app'));
}
Once we've declared our application configuration, we need to start()
the application at a specific root. In the case of the template, our app.ts
is where we want our entry point of our application. For those unfamiliar with Aurelia, this is where we start to see Aurelia's conventions of using plain TypeScript classes and matching app.ts
and app.html
file names which is also used for views.
The app.ts
is just a class with a configureRouter
function for client side routing, it doesn't extend
or implement
and special Aurelia base class or interface.
import {Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config, router: Router) {
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: './views/home', nav: true, title: 'Home' },
{ route: ['/view1', 'view1'], name: 'view1', moduleId: './views/view1', nav: true, title: 'View 1' },
{ route: ['/view2', 'view2'], name: 'view2', moduleId: './views/view2', nav: true, title: 'View 2' }
]);
this.router = router;
}
}
The config
being passed as the first argument is being setup with a title
property. which sets the page <title>
, and a map
which the router can use to direct different client paths to different views with a sub title. For example, when the client navigates to /#/view1
the <title>
of the page is Aurelia | View 1
.
The app.html
provides the related UI for app.ts
which the template is using as a landing page. The router uses the known navigation paths to generate the different links using Aurelia's templating syntax.
<ul class="nav navbar-nav">
<li repeat.for="row of router.navigation" class="${row.isActive ? 'active' : ''}">
<a href.bind="row.href">
${row.title}
</a>
</li>
</ul>
The replace the body with the different views associated with each route, app.html
has also declared a <router-view>
element.
<div class="container">
<router-view></router-view>
</div>
Like all MV* UI frameworks, we need a way to show data (our model) on a page (our view). Aurelia takes a simple default convention approach to match our models and views which is the the name of the file. In the Aurelia template, we have 3 example views, home
, view1
and view2
.
By default they are separated into a
Views
folder, however this is not required or part of any convention, just a simple way to group them.
Again, this is where Aurelia's approach of simple conventions has it's advantages. The hello.ts
model is just a class and referring to properties of data can be done simply by name.
export class Home {
message: string;
constructor() {
this.message = "Home";
}
}
<template>
<h2>${message}</h2>
<hello></hello>
</template>
Though this is just displaying a message on a page, the simplicity of binding, events, dependency injection and other functionality is used with very little ceremony. The use of a custom element <hello>
comes from the template's resources
'feature and because the feature is registered in our application configuration, it's available in any view without additional work.
Although this template only has one sample hello
custom element in the resources
feature, this pattern is a recommended approach by Aurelia team as a way to isolate your internally reusable components for your application. The common types of reusable components Aurelia has are setup with there own directory structure within the project template.
The hello
custom element is a UI component that simply takes a name from an text input, calls the example ServiceStack service with that name, gets a hello message in return and displays it. Just like views, this component has a matching .ts
and .html
files.
import {bindable, autoinject} from "aurelia-framework";
import {HttpClient} from "aurelia-http-client";
@autoinject()
export class HelloCustomElement {
result: string;
@bindable name: string;
constructor(private httpClient: HttpClient) {
this.httpClient.configure(config => {
config.withHeader('Accept', 'application/json');
});
}
nameChanged(newValue) {
if (newValue.length > 0) {
this.httpClient.get('/hello/' + newValue).then((response) => {
this.result = response.content.Result;
});
} else {
this.result = '';
}
}
}
The hello.ts
file above uses a few Aurelia conventions and decorators (eg, @autoinject
) to make this very clean and concise. The class name itself ends with the suffix CustomElement
, which Aurelia detects and strips to get the dash-case
element name of <hello>
when it's used. If the name of the custom element class was SecretMessageCustomElement
, the element usage would be <secret-message>
for example.
The @autoinect
handles dependency injection for our custom element, in this case, the Aurelia HttpClient
is injected and automatically becomes a private
member of our HelloCustomElement
class.
The custom element is also using the @bindable
decorator to allow uses of this custom element to pass data. For example, <hello name="Pat"></hello>
would set the name
to Pat
. Since this custom element also has an input text field using value.bind
this the name value, Pat
would appear in the input text field as well as the initial value.
As a trigger to get the hello message back from our ServiceStack service, we are watching the name
property for changes by simply having a function called nameChanged(newValue)
. To watch any property for changes, as a convention, a function with the same property name prefixed to Changed
needs to be present. Eg, to watch a property foo
a fooChanged(newValue)
function would be declared. As there values are updated, they are bound to our hello.html
view.
<template bindable="name">
<div>
<input class="form-control input-lg" id="Name" value.bind="name" type="text" placeholder="Type your name">
<p>${result}</p>
</div>
</template>
The bindable
attribute in the template is to allow uses of the <hello>
custom element to bind the initial name. These bindable attributes can be though of as a component contract for what data can be bound. The input
text field value is bound to name
by using the value.bind="name"
attribute which triggers our nameChnaged
function when the user types into the field.
The result
string property from our model is then displayed using the Aurelia templating syntax of ${result}
.
Aurelia itself it still in beta and uses a lot of modern web developer tools which are still being worked on and updated by various vendors. Currently there are 2 known issues to a smooth workflow in Visual Studio that are easily worked around and/or a fix will soon be released.
Due to the now 2-year-old version of NodeJS that still ships in Visual Studio External Web Tools, version 0.10.31
, the aurelia-bundler
won't work from Task Runner Explorer from Visual Studio. To resolve this issue, install the latest version of node and change the order of the paths used by Visual Studio to resolve External Web Tools. If NodeJS is installed on the PATH
, it should be listed first like below.
This issue only effects those using ReSharper with Visual Studio. The current latest version of ReSharper still has an outstanding issue which is to be resolved in 2016.2 and the fix is set to release in ReSharper 2016.2 EAP 3.
To resolve the issue, simply disable JavaScript and TypeScript from ReSharper options menu which can be accessed by ReSharper -> Options -> Products & Features -> JavaScript and TypeScript
.
Once this is diabled, these highlighted errors will disapear and TypeScript will continue to compile to JavaScript.
Note, this template requires the TypeScript Visual Studio Extension that uses at least TypeScript 1.8+