Angular ships with its component router. The router makes it easy to compose complex applications from a bunch of components. Multiple routers could easily be nested to achieve almost every requirement for a SPA. When using regular routes, all files from your SPA are transferred to the client as soon as RouteConfig is interpreted by the JavaScript engine of your browser.

With growing apps, you may change this behavior. Clients should only download those files that are required for the current use case. Alternatively, you would initially transfer only those files required to make the core functionality of your app work. Optional components and templates should only be transferred if users request those components explicitly.

This can easily be achieved using AsyncRoutes. The AsyncRoute type is part of the ComponentRouter, by using the AsyncRoute you can specify a custom loader function. The loader function is responsible for requesting the component from the backend. Take the following example as a starting point where all routes are loaded traditionally during the initial RouteConfig execution.

import { Component } from 'angular2/core';
import { ROUTER_DIRECTIVES, RouteConfig } from 'angular2/router';
import { ProductsComponent } from '../products/products';
import { CustomersComponent } from '../customers/customers';
import { SupportCenterComponent } from '../support/supportcenter';
import { AboutComponent } from '../about/about';

@Component({
  selector: 'async-routes-app',
  directives: [ROUTER_DIRECTIVES],
  templateUrl: 'app/components/app/app.html'
})
@RouteConfig([
  {path: '/customers/...', name: 'Customers', useAsDefault: true, component: CustomersComponent},
  {path: '/products/...', name: 'Products', component: ProductsComponent},
  {path: '/support/...', name: 'SupportCenter', component: SupportCenterComponent},
  {path: '/about', name: 'About', component: AboutComponent}
])
export class AppComponent {
}

To get their work done, users need almost every time forms and lists underneath Customers and Products, so it’s a good idea to keep those routes as they are. However, SupportCenter and About routes aren’t business critical; only a few users may use forms and lists sitting behind those routes.

That said, it’s a great idea to use AsyncRoutes to load those pieces only on demand. Let’s change the code from the previous snippet to match those new requirements.

import { Component } from 'angular2/core';
import { ROUTER_DIRECTIVES, RouteConfig, AsyncRoute } from 'angular2/router';
import { ProductsComponent } from '../products/products';
import { CustomersComponent } from '../customers/customers';

@Component({
  selector: 'async-routes-app',
  directives: [ROUTER_DIRECTIVES],
  templateUrl: 'app/components/app/app.html'
})
@RouteConfig([
  {path: '/customers/...', name: 'Customers', useAsDefault: true, component: CustomersComponent},
  {path: '/products/...', name: 'Products', component: ProductsComponent},
  new AsyncRoute({
    path: '/support/...',
    name: 'SupportCenter',
    loader: () => System.import('app/components/support/supportcenter').then(m=> m.SupportCenterComponent)
  }),
  new AsyncRoute({
    path: '/about',
    name: 'About',
    loader: () => {
      return System.import('app/components/about/about')
        .then(m=> m.AboutComponent);
    }
  })
])
export class AppComponent {
}

Here are a few exciting things happening now. First, we’ve added AsyncRoute to the import statements and pulled it from angular2/router. We’ve removed the import statements for both AboutComponent and SupportCenterComponent because those Components load dynamically now.

The most significant changes are made to @RouteConfig. We removed two regular routes and introduced two AsyncRoute implementations.

Take a look at both variants used for the loader function. For SupportCenter our expression is implicitly returning the Promise provided by System.import().then(). It’s important that you remember to return the promise! That’s why I’ve added the verbose syntax for the second AsyncRoute.

Once SystemJS has finished loading the files from the given URL, we’ve to specify the Component or artifact we want to use from our bundle. In both cases we’re – of course – using our required components (m.SupportCenterComponent and m.AboutComponent).

Angular assigns the payload from the promise to the route’s component property once the promise has resolved.

When transpiling those TypeScript sources to JavaScript right now, you may receive some errors because TSD doesn’t know System.import. Unfortunately, there are no definition files available for SystemJS at this point. Either we sit down and write our definition file right now – which is time-consuming – or we tell TSD to track System as a variable of type any. It’s not the cleanest solution, but it’s the simplest to get things working now.

To do so, add the following statement to your TypeScript file (I prefer such statements underneath all import statements.)

declare var System: any;

There is more. Check out Minko’s post, he describes in detail how to create virtual proxies to push async loading to another level.