diff --git a/modules/angular1_router/build.js b/modules/angular1_router/build.js
index ddaacbaa67..5d5065bd86 100644
--- a/modules/angular1_router/build.js
+++ b/modules/angular1_router/build.js
@@ -6,12 +6,13 @@ var ts = require('typescript');
 var files = [
   'lifecycle_annotations_impl.ts',
   'url_parser.ts',
-  'path_recognizer.ts',
+  'route_recognizer.ts',
   'route_config_impl.ts',
   'async_route_handler.ts',
   'sync_route_handler.ts',
-  'route_recognizer.ts',
+  'component_recognizer.ts',
   'instruction.ts',
+  'path_recognizer.ts',
   'route_config_nomalizer.ts',
   'route_lifecycle_reflector.ts',
   'route_registry.ts',
@@ -39,7 +40,10 @@ function main() {
  * sourcemap, and exported variable identifier name for the content.
  */
 var IMPORT_RE = new RegExp("import \\{?([\\w\\n_, ]+)\\}? from '(.+)';?", 'g');
+var INJECT_RE = new RegExp("@Inject\\(ROUTER_PRIMARY_COMPONENT\\)", 'g');
+var IMJECTABLE_RE = new RegExp("@Injectable\\(\\)", 'g');
 function transform(contents) {
+  contents = contents.replace(INJECT_RE, '').replace(IMJECTABLE_RE, '');
   contents = contents.replace(IMPORT_RE, function (match, imports, includePath) {
     //TODO: remove special-case
     if (isFacadeModule(includePath) || includePath === './router_outlet') {
diff --git a/modules/angular1_router/lib/facades.es5 b/modules/angular1_router/lib/facades.es5
index f60f3b986d..b82d198056 100644
--- a/modules/angular1_router/lib/facades.es5
+++ b/modules/angular1_router/lib/facades.es5
@@ -173,6 +173,10 @@ var StringMapWrapper = {
 
 var List = Array;
 var ListWrapper = {
+  clear: function (l) {
+    l.length = 0;
+  },
+
   create: function () {
     return [];
   },
diff --git a/modules/angular1_router/src/module_template.js b/modules/angular1_router/src/module_template.js
index aeed65ea3e..6712ca77c8 100644
--- a/modules/angular1_router/src/module_template.js
+++ b/modules/angular1_router/src/module_template.js
@@ -9,7 +9,11 @@ function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootSc
   // the contents of `../lib/facades.es5`.
   //{{FACADES}}
 
-  var exports = {Injectable: function () {}};
+  var exports = {
+    Injectable: function () {},
+    OpaqueToken: function () {},
+    Inject: function () {}
+  };
   var require = function () {return exports;};
 
   // When this file is processed, the line below is replaced with
@@ -31,12 +35,19 @@ function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootSc
   // property in a route config
   exports.assertComponentExists = function () {};
 
-  angular.stringifyInstruction = exports.stringifyInstruction;
+  angular.stringifyInstruction = function (instruction) {
+    return instruction.toRootUrl();
+  };
 
   var RouteRegistry = exports.RouteRegistry;
   var RootRouter = exports.RootRouter;
 
-  var registry = new RouteRegistry();
+
+  // Because Angular 1 has no notion of a root component, we use an object with unique identity
+  // to represent this.
+  var ROOT_COMPONENT_OBJECT = new Object();
+
+  var registry = new RouteRegistry(ROOT_COMPONENT_OBJECT);
   var location = new Location();
 
   $$directiveIntrospector(function (name, factory) {
@@ -47,10 +58,6 @@ function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootSc
     }
   });
 
-  // Because Angular 1 has no notion of a root component, we use an object with unique identity
-  // to represent this.
-  var ROOT_COMPONENT_OBJECT = new Object();
-
   var router = new RootRouter(registry, location, ROOT_COMPONENT_OBJECT);
   $rootScope.$watch(function () { return $location.path(); }, function (path) {
     if (router.lastNavigationAttempt !== path) {
diff --git a/modules/angular1_router/src/ng_route_shim.js b/modules/angular1_router/src/ng_route_shim.js
index 0d3f245b28..5ba7aa4fe7 100644
--- a/modules/angular1_router/src/ng_route_shim.js
+++ b/modules/angular1_router/src/ng_route_shim.js
@@ -110,7 +110,7 @@
       routeMap[path] = routeCopy;
 
       if (route.redirectTo) {
-        routeDefinition.redirectTo = route.redirectTo;
+        routeDefinition.redirectTo = [routeMap[route.redirectTo].name];
       } else {
         if (routeCopy.controller && !routeCopy.controllerAs) {
           console.warn('Route for "' + path + '" should use "controllerAs".');
@@ -123,7 +123,7 @@
         }
 
         routeDefinition.component = directiveName;
-        routeDefinition.as = upperCase(directiveName);
+        routeDefinition.name = route.name || upperCase(directiveName);
 
         var directiveController = routeCopy.controller;
 
diff --git a/modules/angular1_router/test/integration/navigation_spec.js b/modules/angular1_router/test/integration/navigation_spec.js
index 1a82f197c6..76b9490bab 100644
--- a/modules/angular1_router/test/integration/navigation_spec.js
+++ b/modules/angular1_router/test/integration/navigation_spec.js
@@ -113,8 +113,7 @@ describe('navigation', function () {
   });
 
 
-  // TODO: fix this
-  xit('should work with recursive nested outlets', function () {
+  it('should work with recursive nested outlets', function () {
     registerComponent('recurCmp', {
       template: '
',
       $routeConfig: [
@@ -152,8 +151,8 @@ describe('navigation', function () {
     compile('');
 
     $router.config([
-      { path: '/',     redirectTo: '/user' },
-      { path: '/user', component:  'userCmp' }
+      { path: '/',     redirectTo: ['/User'] },
+      { path: '/user', component:  'userCmp', name: 'User' }
     ]);
 
     $router.navigateByUrl('/');
@@ -167,16 +166,15 @@ describe('navigation', function () {
     registerComponent('childRouter', {
       template: '',
       $routeConfig: [
-        { path: '/old-child', redirectTo: '/new-child' },
-        { path: '/new-child', component: 'oneCmp'},
-        { path: '/old-child-two', redirectTo: '/new-child-two' },
-        { path: '/new-child-two', component: 'twoCmp'}
+        { path: '/new-child', component: 'oneCmp', name: 'NewChild'},
+        { path: '/new-child-two', component: 'twoCmp', name: 'NewChildTwo'}
       ]
     });
 
     $router.config([
-      { path: '/old-parent', redirectTo: '/new-parent' },
-      { path: '/new-parent/...', component:  'childRouter' }
+      { path: '/old-parent/old-child', redirectTo: ['/NewParent', 'NewChild'] },
+      { path: '/old-parent/old-child-two', redirectTo: ['/NewParent', 'NewChildTwo'] },
+      { path: '/new-parent/...', component:  'childRouter', name: 'NewParent' }
     ]);
 
     compile('');
diff --git a/modules/angular1_router/test/integration/shim_spec.js b/modules/angular1_router/test/integration/shim_spec.js
index f074bd50fd..e2edffe552 100644
--- a/modules/angular1_router/test/integration/shim_spec.js
+++ b/modules/angular1_router/test/integration/shim_spec.js
@@ -139,11 +139,12 @@ describe('ngRoute shim', function () {
 
   it('should adapt routes with redirects', inject(function ($location) {
     $routeProvider
+        .when('/home', {
+          template: 'welcome home!',
+          name: 'Home'
+        })
         .when('/', {
           redirectTo: '/home'
-        })
-        .when('/home', {
-          template: 'welcome home!'
         });
     $rootScope.$digest();
 
diff --git a/modules/angular2/router.ts b/modules/angular2/router.ts
index 3bc054c9b7..b329aa8a0d 100644
--- a/modules/angular2/router.ts
+++ b/modules/angular2/router.ts
@@ -8,8 +8,8 @@ export {Router} from './src/router/router';
 export {RouterOutlet} from './src/router/router_outlet';
 export {RouterLink} from './src/router/router_link';
 export {RouteParams, RouteData} from './src/router/instruction';
-export {RouteRegistry} from './src/router/route_registry';
 export {PlatformLocation} from './src/router/platform_location';
+export {RouteRegistry, ROUTER_PRIMARY_COMPONENT} from './src/router/route_registry';
 export {LocationStrategy, APP_BASE_HREF} from './src/router/location_strategy';
 export {HashLocationStrategy} from './src/router/hash_location_strategy';
 export {PathLocationStrategy} from './src/router/path_location_strategy';
@@ -27,41 +27,12 @@ import {PathLocationStrategy} from './src/router/path_location_strategy';
 import {Router, RootRouter} from './src/router/router';
 import {RouterOutlet} from './src/router/router_outlet';
 import {RouterLink} from './src/router/router_link';
-import {RouteRegistry} from './src/router/route_registry';
+import {RouteRegistry, ROUTER_PRIMARY_COMPONENT} from './src/router/route_registry';
 import {Location} from './src/router/location';
 import {ApplicationRef, provide, OpaqueToken, Provider} from 'angular2/core';
 import {CONST_EXPR} from './src/facade/lang';
 import {BaseException} from 'angular2/src/facade/exceptions';
 
-
-/**
- * Token used to bind the component with the top-level {@link RouteConfig}s for the
- * application.
- *
- * ### Example ([live demo](http://plnkr.co/edit/iRUP8B5OUbxCWQ3AcIDm))
- *
- * ```
- * import {Component} from 'angular2/angular2';
- * import {
- *   ROUTER_DIRECTIVES,
- *   ROUTER_PROVIDERS,
- *   RouteConfig
- * } from 'angular2/router';
- *
- * @Component({directives: [ROUTER_DIRECTIVES]})
- * @RouteConfig([
- *  {...},
- * ])
- * class AppCmp {
- *   // ...
- * }
- *
- * bootstrap(AppCmp, [ROUTER_PROVIDERS]);
- * ```
- */
-export const ROUTER_PRIMARY_COMPONENT: OpaqueToken =
-    CONST_EXPR(new OpaqueToken('RouterPrimaryComponent'));
-
 /**
  * A list of directives. To use the router directives like {@link RouterOutlet} and
  * {@link RouterLink}, add this to your `directives` array in the {@link View} decorator of your
diff --git a/modules/angular2/src/router/async_route_handler.ts b/modules/angular2/src/router/async_route_handler.ts
index 6e22218344..a739352151 100644
--- a/modules/angular2/src/router/async_route_handler.ts
+++ b/modules/angular2/src/router/async_route_handler.ts
@@ -1,13 +1,19 @@
-import {RouteHandler} from './route_handler';
 import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
 import {isPresent, Type} from 'angular2/src/facade/lang';
 
+import {RouteHandler} from './route_handler';
+import {RouteData, BLANK_ROUTE_DATA} from './instruction';
+
+
 export class AsyncRouteHandler implements RouteHandler {
   /** @internal */
   _resolvedComponent: Promise = null;
   componentType: Type;
+  public data: RouteData;
 
-  constructor(private _loader: Function, public data?: {[key: string]: any}) {}
+  constructor(private _loader: Function, data: {[key: string]: any} = null) {
+    this.data = isPresent(data) ? new RouteData(data) : BLANK_ROUTE_DATA;
+  }
 
   resolveComponentType(): Promise {
     if (isPresent(this._resolvedComponent)) {
diff --git a/modules/angular2/src/router/component_recognizer.ts b/modules/angular2/src/router/component_recognizer.ts
new file mode 100644
index 0000000000..56730566fd
--- /dev/null
+++ b/modules/angular2/src/router/component_recognizer.ts
@@ -0,0 +1,157 @@
+import {isBlank, isPresent} from 'angular2/src/facade/lang';
+import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
+import {Map, MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
+import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
+
+import {
+  AbstractRecognizer,
+  RouteRecognizer,
+  RedirectRecognizer,
+  RouteMatch
+} from './route_recognizer';
+import {Route, AsyncRoute, AuxRoute, Redirect, RouteDefinition} from './route_config_impl';
+import {AsyncRouteHandler} from './async_route_handler';
+import {SyncRouteHandler} from './sync_route_handler';
+import {Url} from './url_parser';
+import {ComponentInstruction} from './instruction';
+
+
+/**
+ * `ComponentRecognizer` is responsible for recognizing routes for a single component.
+ * It is consumed by `RouteRegistry`, which knows how to recognize an entire hierarchy of
+ * components.
+ */
+export class ComponentRecognizer {
+  names = new Map();
+
+  // map from name to recognizer
+  auxNames = new Map();
+
+  // map from starting path to recognizer
+  auxRoutes = new Map();
+
+  // TODO: optimize this into a trie
+  matchers: AbstractRecognizer[] = [];
+
+  defaultRoute: RouteRecognizer = null;
+
+  /**
+   * returns whether or not the config is terminal
+   */
+  config(config: RouteDefinition): boolean {
+    var handler;
+
+    if (isPresent(config.name) && config.name[0].toUpperCase() != config.name[0]) {
+      var suggestedName = config.name[0].toUpperCase() + config.name.substring(1);
+      throw new BaseException(
+          `Route "${config.path}" with name "${config.name}" does not begin with an uppercase letter. Route names should be CamelCase like "${suggestedName}".`);
+    }
+
+    if (config instanceof AuxRoute) {
+      handler = new SyncRouteHandler(config.component, config.data);
+      let path = config.path.startsWith('/') ? config.path.substring(1) : config.path;
+      var recognizer = new RouteRecognizer(config.path, handler);
+      this.auxRoutes.set(path, recognizer);
+      if (isPresent(config.name)) {
+        this.auxNames.set(config.name, recognizer);
+      }
+      return recognizer.terminal;
+    }
+
+    var useAsDefault = false;
+
+    if (config instanceof Redirect) {
+      let redirector = new RedirectRecognizer(config.path, config.redirectTo);
+      this._assertNoHashCollision(redirector.hash, config.path);
+      this.matchers.push(redirector);
+      return true;
+    }
+
+    if (config instanceof Route) {
+      handler = new SyncRouteHandler(config.component, config.data);
+      useAsDefault = isPresent(config.useAsDefault) && config.useAsDefault;
+    } else if (config instanceof AsyncRoute) {
+      handler = new AsyncRouteHandler(config.loader, config.data);
+      useAsDefault = isPresent(config.useAsDefault) && config.useAsDefault;
+    }
+    var recognizer = new RouteRecognizer(config.path, handler);
+
+    this._assertNoHashCollision(recognizer.hash, config.path);
+
+    if (useAsDefault) {
+      if (isPresent(this.defaultRoute)) {
+        throw new BaseException(`Only one route can be default`);
+      }
+      this.defaultRoute = recognizer;
+    }
+
+    this.matchers.push(recognizer);
+    if (isPresent(config.name)) {
+      this.names.set(config.name, recognizer);
+    }
+    return recognizer.terminal;
+  }
+
+
+  private _assertNoHashCollision(hash: string, path) {
+    this.matchers.forEach((matcher) => {
+      if (hash == matcher.hash) {
+        throw new BaseException(
+            `Configuration '${path}' conflicts with existing route '${matcher.path}'`);
+      }
+    });
+  }
+
+
+  /**
+   * Given a URL, returns a list of `RouteMatch`es, which are partial recognitions for some route.
+   */
+  recognize(urlParse: Url): Promise[] {
+    var solutions = [];
+
+    this.matchers.forEach((routeRecognizer: AbstractRecognizer) => {
+      var pathMatch = routeRecognizer.recognize(urlParse);
+
+      if (isPresent(pathMatch)) {
+        solutions.push(pathMatch);
+      }
+    });
+
+    return solutions;
+  }
+
+  recognizeAuxiliary(urlParse: Url): Promise[] {
+    var routeRecognizer: RouteRecognizer = this.auxRoutes.get(urlParse.path);
+    if (isPresent(routeRecognizer)) {
+      return [routeRecognizer.recognize(urlParse)];
+    }
+
+    return [PromiseWrapper.resolve(null)];
+  }
+
+  hasRoute(name: string): boolean { return this.names.has(name); }
+
+  componentLoaded(name: string): boolean {
+    return this.hasRoute(name) && isPresent(this.names.get(name).handler.componentType);
+  }
+
+  loadComponent(name: string): Promise {
+    return this.names.get(name).handler.resolveComponentType();
+  }
+
+  generate(name: string, params: any): ComponentInstruction {
+    var pathRecognizer: RouteRecognizer = this.names.get(name);
+    if (isBlank(pathRecognizer)) {
+      return null;
+    }
+    return pathRecognizer.generate(params);
+  }
+
+  generateAuxiliary(name: string, params: any): ComponentInstruction {
+    var pathRecognizer: RouteRecognizer = this.auxNames.get(name);
+    if (isBlank(pathRecognizer)) {
+      return null;
+    }
+    return pathRecognizer.generate(params);
+  }
+}
diff --git a/modules/angular2/src/router/instruction.ts b/modules/angular2/src/router/instruction.ts
index 760394c65e..13e4e9f192 100644
--- a/modules/angular2/src/router/instruction.ts
+++ b/modules/angular2/src/router/instruction.ts
@@ -1,10 +1,7 @@
 import {Map, MapWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
-import {unimplemented} from 'angular2/src/facade/exceptions';
 import {isPresent, isBlank, normalizeBlank, Type, CONST_EXPR} from 'angular2/src/facade/lang';
-import {Promise} from 'angular2/src/facade/async';
+import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
 
-import {PathRecognizer} from './path_recognizer';
-import {Url} from './url_parser';
 
 /**
  * `RouteParams` is an immutable map of parameters for the given route
@@ -77,7 +74,7 @@ export class RouteData {
   get(key: string): any { return normalizeBlank(StringMapWrapper.get(this.data, key)); }
 }
 
-var BLANK_ROUTE_DATA = new RouteData();
+export var BLANK_ROUTE_DATA = new RouteData();
 
 /**
  * `Instruction` is a tree of {@link ComponentInstruction}s with all the information needed
@@ -106,74 +103,184 @@ var BLANK_ROUTE_DATA = new RouteData();
  * bootstrap(AppCmp, ROUTER_PROVIDERS);
  * ```
  */
-export class Instruction {
-  constructor(public component: ComponentInstruction, public child: Instruction,
-              public auxInstruction: {[key: string]: Instruction}) {}
+export abstract class Instruction {
+  public component: ComponentInstruction;
+  public child: Instruction;
+  public auxInstruction: {[key: string]: Instruction} = {};
+
+  get urlPath(): string { return this.component.urlPath; }
+
+  get urlParams(): string[] { return this.component.urlParams; }
+
+  get specificity(): number {
+    var total = 0;
+    if (isPresent(this.component)) {
+      total += this.component.specificity;
+    }
+    if (isPresent(this.child)) {
+      total += this.child.specificity;
+    }
+    return total;
+  }
+
+  abstract resolveComponent(): Promise;
+
+  /**
+   * converts the instruction into a URL string
+   */
+  toRootUrl(): string { return this.toUrlPath() + this.toUrlQuery(); }
+
+  /** @internal */
+  _toNonRootUrl(): string {
+    return this._stringifyPathMatrixAuxPrefixed() +
+           (isPresent(this.child) ? this.child._toNonRootUrl() : '');
+  }
+
+  toUrlQuery(): string { return this.urlParams.length > 0 ? ('?' + this.urlParams.join('&')) : ''; }
 
   /**
    * Returns a new instruction that shares the state of the existing instruction, but with
    * the given child {@link Instruction} replacing the existing child.
    */
   replaceChild(child: Instruction): Instruction {
-    return new Instruction(this.component, child, this.auxInstruction);
+    return new ResolvedInstruction(this.component, child, this.auxInstruction);
   }
-}
 
-/**
- * Represents a partially completed instruction during recognition that only has the
- * primary (non-aux) route instructions matched.
- *
- * `PrimaryInstruction` is an internal class used by `RouteRecognizer` while it's
- * figuring out where to navigate.
- */
-export class PrimaryInstruction {
-  constructor(public component: ComponentInstruction, public child: PrimaryInstruction,
-              public auxUrls: Url[]) {}
-}
-
-export function stringifyInstruction(instruction: Instruction): string {
-  return stringifyInstructionPath(instruction) + stringifyInstructionQuery(instruction);
-}
-
-export function stringifyInstructionPath(instruction: Instruction): string {
-  return instruction.component.urlPath + stringifyAux(instruction) +
-         stringifyPrimaryPrefixed(instruction.child);
-}
-
-export function stringifyInstructionQuery(instruction: Instruction): string {
-  return instruction.component.urlParams.length > 0 ?
-             ('?' + instruction.component.urlParams.join('&')) :
-             '';
-}
-
-function stringifyPrimaryPrefixed(instruction: Instruction): string {
-  var primary = stringifyPrimary(instruction);
-  if (primary.length > 0) {
-    primary = '/' + primary;
+  /**
+   * If the final URL for the instruction is ``
+   */
+  toUrlPath(): string {
+    return this.urlPath + this._stringifyAux() +
+           (isPresent(this.child) ? this.child._toNonRootUrl() : '');
   }
-  return primary;
-}
 
-function stringifyPrimary(instruction: Instruction): string {
-  if (isBlank(instruction)) {
+  // default instructions override these
+  toLinkUrl(): string {
+    return this.urlPath + this._stringifyAux() +
+           (isPresent(this.child) ? this.child._toLinkUrl() : '');
+  }
+
+  // this is the non-root version (called recursively)
+  /** @internal */
+  _toLinkUrl(): string {
+    return this._stringifyPathMatrixAuxPrefixed() +
+           (isPresent(this.child) ? this.child._toLinkUrl() : '');
+  }
+
+  /** @internal */
+  _stringifyPathMatrixAuxPrefixed(): string {
+    var primary = this._stringifyPathMatrixAux();
+    if (primary.length > 0) {
+      primary = '/' + primary;
+    }
+    return primary;
+  }
+
+  /** @internal */
+  _stringifyMatrixParams(): string {
+    return this.urlParams.length > 0 ? (';' + this.component.urlParams.join(';')) : '';
+  }
+
+  /** @internal */
+  _stringifyPathMatrixAux(): string {
+    if (isBlank(this.component)) {
+      return '';
+    }
+    return this.urlPath + this._stringifyMatrixParams() + this._stringifyAux();
+  }
+
+  /** @internal */
+  _stringifyAux(): string {
+    var routes = [];
+    StringMapWrapper.forEach(this.auxInstruction, (auxInstruction, _) => {
+      routes.push(auxInstruction._stringifyPathMatrixAux());
+    });
+    if (routes.length > 0) {
+      return '(' + routes.join('//') + ')';
+    }
     return '';
   }
-  var params = instruction.component.urlParams.length > 0 ?
-                   (';' + instruction.component.urlParams.join(';')) :
-                   '';
-  return instruction.component.urlPath + params + stringifyAux(instruction) +
-         stringifyPrimaryPrefixed(instruction.child);
 }
 
-function stringifyAux(instruction: Instruction): string {
-  var routes = [];
-  StringMapWrapper.forEach(instruction.auxInstruction, (auxInstruction, _) => {
-    routes.push(stringifyPrimary(auxInstruction));
-  });
-  if (routes.length > 0) {
-    return '(' + routes.join('//') + ')';
+
+/**
+ * a resolved instruction has an outlet instruction for itself, but maybe not for...
+ */
+export class ResolvedInstruction extends Instruction {
+  constructor(public component: ComponentInstruction, public child: Instruction,
+              public auxInstruction: {[key: string]: Instruction}) {
+    super();
+  }
+
+  resolveComponent(): Promise {
+    return PromiseWrapper.resolve(this.component);
+  }
+}
+
+
+/**
+ * Represents a resolved default route
+ */
+export class DefaultInstruction extends Instruction {
+  constructor(public component: ComponentInstruction, public child: DefaultInstruction) { super(); }
+
+  resolveComponent(): Promise {
+    return PromiseWrapper.resolve(this.component);
+  }
+
+  toLinkUrl(): string { return ''; }
+
+  /** @internal */
+  _toLinkUrl(): string { return ''; }
+}
+
+
+/**
+ * Represents a component that may need to do some redirection or lazy loading at a later time.
+ */
+export class UnresolvedInstruction extends Instruction {
+  constructor(private _resolver: () => Promise, private _urlPath: string = '',
+              private _urlParams: string[] = CONST_EXPR([])) {
+    super();
+  }
+
+  get urlPath(): string {
+    if (isPresent(this.component)) {
+      return this.component.urlPath;
+    }
+    if (isPresent(this._urlPath)) {
+      return this._urlPath;
+    }
+    return '';
+  }
+
+  get urlParams(): string[] {
+    if (isPresent(this.component)) {
+      return this.component.urlParams;
+    }
+    if (isPresent(this._urlParams)) {
+      return this._urlParams;
+    }
+    return [];
+  }
+
+  resolveComponent(): Promise {
+    if (isPresent(this.component)) {
+      return PromiseWrapper.resolve(this.component);
+    }
+    return this._resolver().then((resolution: Instruction) => {
+      this.child = resolution.child;
+      return this.component = resolution.component;
+    });
+  }
+}
+
+
+export class RedirectInstruction extends ResolvedInstruction {
+  constructor(component: ComponentInstruction, child: Instruction,
+              auxInstruction: {[key: string]: Instruction}) {
+    super(component, child, auxInstruction);
   }
-  return '';
 }
 
 
@@ -185,67 +292,18 @@ function stringifyAux(instruction: Instruction): string {
  * to route lifecycle hooks, like {@link CanActivate}.
  *
  * `ComponentInstruction`s are [https://en.wikipedia.org/wiki/Hash_consing](hash consed). You should
- * never construct one yourself with "new." Instead, rely on {@link Router/PathRecognizer} to
+ * never construct one yourself with "new." Instead, rely on {@link Router/RouteRecognizer} to
  * construct `ComponentInstruction`s.
  *
  * You should not modify this object. It should be treated as immutable.
  */
-export abstract class ComponentInstruction {
+export class ComponentInstruction {
   reuse: boolean = false;
-  public urlPath: string;
-  public urlParams: string[];
-  public params: {[key: string]: any};
+  public routeData: RouteData;
 
-  /**
-   * Returns the component type of the represented route, or `null` if this instruction
-   * hasn't been resolved.
-   */
-  get componentType() { return unimplemented(); };
-
-  /**
-   * Returns a promise that will resolve to component type of the represented route.
-   * If this instruction references an {@link AsyncRoute}, the `loader` function of that route
-   * will run.
-   */
-  abstract resolveComponentType(): Promise;
-
-  /**
-   * Returns the specificity of the route associated with this `Instruction`.
-   */
-  get specificity() { return unimplemented(); };
-
-  /**
-   * Returns `true` if the component type of this instruction has no child {@link RouteConfig},
-   * or `false` if it does.
-   */
-  get terminal() { return unimplemented(); };
-
-  /**
-   * Returns the route data of the given route that was specified in the {@link RouteDefinition},
-   * or an empty object if no route data was specified.
-   */
-  get routeData(): RouteData { return unimplemented(); };
-}
-
-export class ComponentInstruction_ extends ComponentInstruction {
-  private _routeData: RouteData;
-
-  constructor(urlPath: string, urlParams: string[], private _recognizer: PathRecognizer,
-              params: {[key: string]: any} = null) {
-    super();
-    this.urlPath = urlPath;
-    this.urlParams = urlParams;
-    this.params = params;
-    if (isPresent(this._recognizer.handler.data)) {
-      this._routeData = new RouteData(this._recognizer.handler.data);
-    } else {
-      this._routeData = BLANK_ROUTE_DATA;
-    }
+  constructor(public urlPath: string, public urlParams: string[], data: RouteData,
+              public componentType, public terminal: boolean, public specificity: number,
+              public params: {[key: string]: any} = null) {
+    this.routeData = isPresent(data) ? data : BLANK_ROUTE_DATA;
   }
-
-  get componentType() { return this._recognizer.handler.componentType; }
-  resolveComponentType(): Promise { return this._recognizer.handler.resolveComponentType(); }
-  get specificity() { return this._recognizer.specificity; }
-  get terminal() { return this._recognizer.terminal; }
-  get routeData(): RouteData { return this._routeData; }
 }
diff --git a/modules/angular2/src/router/path_recognizer.ts b/modules/angular2/src/router/path_recognizer.ts
index 01e84e0cc0..1e03e1271d 100644
--- a/modules/angular2/src/router/path_recognizer.ts
+++ b/modules/angular2/src/router/path_recognizer.ts
@@ -7,12 +7,9 @@ import {
   isBlank
 } from 'angular2/src/facade/lang';
 import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
-
 import {Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
 
-import {RouteHandler} from './route_handler';
 import {Url, RootUrl, serializeParams} from './url_parser';
-import {ComponentInstruction, ComponentInstruction_} from './instruction';
 
 class TouchMap {
   map: {[key: string]: string} = {};
@@ -33,7 +30,7 @@ class TouchMap {
   }
 
   getUnused(): {[key: string]: any} {
-    var unused: {[key: string]: any} = StringMapWrapper.create();
+    var unused: {[key: string]: any} = {};
     var keys = StringMapWrapper.keys(this.keys);
     keys.forEach(key => unused[key] = StringMapWrapper.get(this.map, key));
     return unused;
@@ -126,7 +123,6 @@ function parsePathString(route: string): {[key: string]: any} {
       results.push(new StarSegment(match[1]));
     } else if (segment == '...') {
       if (i < limit) {
-        // TODO (matsko): setup a proper error here `
         throw new BaseException(`Unexpected "..." before the end of the path for "${route}".`);
       }
       results.push(new ContinuationSegment());
@@ -175,23 +171,17 @@ function assertPath(path: string) {
   }
 }
 
-export class PathMatch {
-  constructor(public instruction: ComponentInstruction, public remaining: Url,
-              public remainingAux: Url[]) {}
-}
 
-// represents something like '/foo/:bar'
+/**
+ * Parses a URL string using a given matcher DSL, and generates URLs from param maps
+ */
 export class PathRecognizer {
   private _segments: Segment[];
   specificity: number;
   terminal: boolean = true;
   hash: string;
-  private _cache: Map = new Map();
 
-
-  // TODO: cache component instruction instances by params and by ParsedUrl instance
-
-  constructor(public path: string, public handler: RouteHandler) {
+  constructor(public path: string) {
     assertPath(path);
     var parsed = parsePathString(path);
 
@@ -203,8 +193,7 @@ export class PathRecognizer {
     this.terminal = !(lastSegment instanceof ContinuationSegment);
   }
 
-
-  recognize(beginningSegment: Url): PathMatch {
+  recognize(beginningSegment: Url): {[key: string]: any} {
     var nextSegment = beginningSegment;
     var currentSegment: Url;
     var positionalParams = {};
@@ -247,7 +236,6 @@ export class PathRecognizer {
     var urlPath = captured.join('/');
 
     var auxiliary;
-    var instruction: ComponentInstruction;
     var urlParams;
     var allParams;
     if (isPresent(currentSegment)) {
@@ -267,12 +255,11 @@ export class PathRecognizer {
       auxiliary = [];
       urlParams = [];
     }
-    instruction = this._getInstruction(urlPath, urlParams, this, allParams);
-    return new PathMatch(instruction, nextSegment, auxiliary);
+    return {urlPath, urlParams, allParams, auxiliary, nextSegment};
   }
 
 
-  generate(params: {[key: string]: any}): ComponentInstruction {
+  generate(params: {[key: string]: any}): {[key: string]: any} {
     var paramTokens = new TouchMap(params);
 
     var path = [];
@@ -288,18 +275,6 @@ export class PathRecognizer {
     var nonPositionalParams = paramTokens.getUnused();
     var urlParams = serializeParams(nonPositionalParams);
 
-    return this._getInstruction(urlPath, urlParams, this, params);
-  }
-
-  private _getInstruction(urlPath: string, urlParams: string[], _recognizer: PathRecognizer,
-                          params: {[key: string]: any}): ComponentInstruction {
-    var hashKey = urlPath + '?' + urlParams.join('?');
-    if (this._cache.has(hashKey)) {
-      return this._cache.get(hashKey);
-    }
-    var instruction = new ComponentInstruction_(urlPath, urlParams, _recognizer, params);
-    this._cache.set(hashKey, instruction);
-
-    return instruction;
+    return {urlPath, urlParams};
   }
 }
diff --git a/modules/angular2/src/router/route_config_impl.ts b/modules/angular2/src/router/route_config_impl.ts
index b56fb0103f..b52de354df 100644
--- a/modules/angular2/src/router/route_config_impl.ts
+++ b/modules/angular2/src/router/route_config_impl.ts
@@ -21,6 +21,8 @@ export class RouteConfig {
  * - `name` is an optional `CamelCase` string representing the name of the route.
  * - `data` is an optional property of any type representing arbitrary route metadata for the given
  * route. It is injectable via {@link RouteData}.
+ * - `useAsDefault` is a boolean value. If `true`, the child route will be navigated to if no child
+ * route is specified during the navigation.
  *
  * ### Example
  * ```
@@ -38,16 +40,20 @@ export class Route implements RouteDefinition {
   path: string;
   component: Type;
   name: string;
+  useAsDefault: boolean;
   // added next three properties to work around https://github.com/Microsoft/TypeScript/issues/4107
   aux: string = null;
   loader: Function = null;
-  redirectTo: string = null;
-  constructor({path, component, name,
-               data}: {path: string, component: Type, name?: string, data?: {[key: string]: any}}) {
+  redirectTo: any[] = null;
+  constructor({path, component, name, data, useAsDefault}: {
+    path: string,
+    component: Type, name?: string, data?: {[key: string]: any}, useAsDefault?: boolean
+  }) {
     this.path = path;
     this.component = component;
     this.name = name;
     this.data = data;
+    this.useAsDefault = useAsDefault;
   }
 }
 
@@ -80,7 +86,8 @@ export class AuxRoute implements RouteDefinition {
   // added next three properties to work around https://github.com/Microsoft/TypeScript/issues/4107
   aux: string = null;
   loader: Function = null;
-  redirectTo: string = null;
+  redirectTo: any[] = null;
+  useAsDefault: boolean = false;
   constructor({path, component, name}: {path: string, component: Type, name?: string}) {
     this.path = path;
     this.component = component;
@@ -98,6 +105,8 @@ export class AuxRoute implements RouteDefinition {
  * - `name` is an optional `CamelCase` string representing the name of the route.
  * - `data` is an optional property of any type representing arbitrary route metadata for the given
  * route. It is injectable via {@link RouteData}.
+ * - `useAsDefault` is a boolean value. If `true`, the child route will be navigated to if no child
+ * route is specified during the navigation.
  *
  * ### Example
  * ```
@@ -115,31 +124,37 @@ export class AsyncRoute implements RouteDefinition {
   path: string;
   loader: Function;
   name: string;
+  useAsDefault: boolean;
   aux: string = null;
-  constructor({path, loader, name, data}:
-                  {path: string, loader: Function, name?: string, data?: {[key: string]: any}}) {
+  constructor({path, loader, name, data, useAsDefault}: {
+    path: string,
+    loader: Function, name?: string, data?: {[key: string]: any}, useAsDefault?: boolean
+  }) {
     this.path = path;
     this.loader = loader;
     this.name = name;
     this.data = data;
+    this.useAsDefault = useAsDefault;
   }
 }
 
 /**
- * `Redirect` is a type of {@link RouteDefinition} used to route a path to an asynchronously loaded
- * component.
+ * `Redirect` is a type of {@link RouteDefinition} used to route a path to a canonical route.
  *
  * It has the following properties:
  * - `path` is a string that uses the route matcher DSL.
- * - `redirectTo` is a string representing the new URL to be matched against.
+ * - `redirectTo` is an array representing the link DSL.
+ *
+ * Note that redirects **do not** affect how links are generated. For that, see the `useAsDefault`
+ * option.
  *
  * ### Example
  * ```
  * import {RouteConfig} from 'angular2/router';
  *
  * @RouteConfig([
- *   {path: '/', redirectTo: '/home'},
- *   {path: '/home', component: HomeCmp}
+ *   {path: '/', redirectTo: ['/Home'] },
+ *   {path: '/home', component: HomeCmp, name: 'Home'}
  * ])
  * class MyApp {}
  * ```
@@ -147,13 +162,14 @@ export class AsyncRoute implements RouteDefinition {
 @CONST()
 export class Redirect implements RouteDefinition {
   path: string;
-  redirectTo: string;
+  redirectTo: any[];
   name: string = null;
   // added next three properties to work around https://github.com/Microsoft/TypeScript/issues/4107
   loader: Function = null;
   data: any = null;
   aux: string = null;
-  constructor({path, redirectTo}: {path: string, redirectTo: string}) {
+  useAsDefault: boolean = false;
+  constructor({path, redirectTo}: {path: string, redirectTo: any[]}) {
     this.path = path;
     this.redirectTo = redirectTo;
   }
diff --git a/modules/angular2/src/router/route_config_nomalizer.dart b/modules/angular2/src/router/route_config_nomalizer.dart
index 2d3005295b..6fe053e62d 100644
--- a/modules/angular2/src/router/route_config_nomalizer.dart
+++ b/modules/angular2/src/router/route_config_nomalizer.dart
@@ -1,9 +1,22 @@
 library angular2.src.router.route_config_normalizer;
 
 import "route_config_decorator.dart";
+import "route_registry.dart";
 import "package:angular2/src/facade/exceptions.dart" show BaseException;
 
-RouteDefinition normalizeRouteConfig(RouteDefinition config) {
+RouteDefinition normalizeRouteConfig(RouteDefinition config, RouteRegistry registry) {
+  if (config is AsyncRoute) {
+
+    configRegistryAndReturnType(componentType) {
+      registry.configFromComponent(componentType);
+      return componentType;
+    }
+
+    loader() {
+      return config.loader().then(configRegistryAndReturnType);
+    }
+    return new AsyncRoute(path: config.path, loader: loader, name: config.name, data: config.data, useAsDefault: config.useAsDefault);
+  }
   return config;
 }
 
diff --git a/modules/angular2/src/router/route_config_nomalizer.ts b/modules/angular2/src/router/route_config_nomalizer.ts
index 8d0725dbab..16518fa79f 100644
--- a/modules/angular2/src/router/route_config_nomalizer.ts
+++ b/modules/angular2/src/router/route_config_nomalizer.ts
@@ -2,14 +2,29 @@ import {AsyncRoute, AuxRoute, Route, Redirect, RouteDefinition} from './route_co
 import {ComponentDefinition} from './route_definition';
 import {isType, Type} from 'angular2/src/facade/lang';
 import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
+import {RouteRegistry} from './route_registry';
 
 
 /**
- * Given a JS Object that represents... returns a corresponding Route, AsyncRoute, or Redirect
+ * Given a JS Object that represents a route config, returns a corresponding Route, AsyncRoute,
+ * AuxRoute or Redirect object.
+ *
+ * Also wraps an AsyncRoute's loader function to add the loaded component's route config to the
+ * `RouteRegistry`.
  */
-export function normalizeRouteConfig(config: RouteDefinition): RouteDefinition {
-  if (config instanceof Route || config instanceof Redirect || config instanceof AsyncRoute ||
-      config instanceof AuxRoute) {
+export function normalizeRouteConfig(config: RouteDefinition,
+                                     registry: RouteRegistry): RouteDefinition {
+  if (config instanceof AsyncRoute) {
+    var wrappedLoader = wrapLoaderToReconfigureRegistry(config.loader, registry);
+    return new AsyncRoute({
+      path: config.path,
+      loader: wrappedLoader,
+      name: config.name,
+      data: config.data,
+      useAsDefault: config.useAsDefault
+    });
+  }
+  if (config instanceof Route || config instanceof Redirect || config instanceof AuxRoute) {
     return config;
   }
 
@@ -24,7 +39,13 @@ export function normalizeRouteConfig(config: RouteDefinition): RouteDefinition {
     config.name = config.as;
   }
   if (config.loader) {
-    return new AsyncRoute({path: config.path, loader: config.loader, name: config.name});
+    var wrappedLoader = wrapLoaderToReconfigureRegistry(config.loader, registry);
+    return new AsyncRoute({
+      path: config.path,
+      loader: wrappedLoader,
+      name: config.name,
+      useAsDefault: config.useAsDefault
+    });
   }
   if (config.aux) {
     return new AuxRoute({path: config.aux, component:config.component, name: config.name});
@@ -36,11 +57,17 @@ export function normalizeRouteConfig(config: RouteDefinition): RouteDefinition {
         return new Route({
           path: config.path,
           component:componentDefinitionObject.constructor,
-          name: config.name
+          name: config.name,
+          data: config.data,
+          useAsDefault: config.useAsDefault
         });
       } else if (componentDefinitionObject.type == 'loader') {
-        return new AsyncRoute(
-            {path: config.path, loader: componentDefinitionObject.loader, name: config.name});
+        return new AsyncRoute({
+          path: config.path,
+          loader: componentDefinitionObject.loader,
+          name: config.name,
+          useAsDefault: config.useAsDefault
+        });
       } else {
         throw new BaseException(
             `Invalid component type "${componentDefinitionObject.type}". Valid types are "constructor" and "loader".`);
@@ -50,6 +77,8 @@ export function normalizeRouteConfig(config: RouteDefinition): RouteDefinition {
       path: string;
       component: Type;
       name?: string;
+      data?: {[key: string]: any};
+      useAsDefault?: boolean;
     }>config);
   }
 
@@ -60,6 +89,16 @@ export function normalizeRouteConfig(config: RouteDefinition): RouteDefinition {
   return config;
 }
 
+
+function wrapLoaderToReconfigureRegistry(loader: Function, registry: RouteRegistry): Function {
+  return () => {
+    return loader().then((componentType) => {
+      registry.configFromComponent(componentType);
+      return componentType;
+    });
+  };
+}
+
 export function assertComponentExists(component: Type, path: string): void {
   if (!isType(component)) {
     throw new BaseException(`Component for route "${path}" is not defined, or is not a class.`);
diff --git a/modules/angular2/src/router/route_definition.dart b/modules/angular2/src/router/route_definition.dart
index e5d0a22539..79c8b5672b 100644
--- a/modules/angular2/src/router/route_definition.dart
+++ b/modules/angular2/src/router/route_definition.dart
@@ -3,5 +3,6 @@ library angular2.src.router.route_definition;
 abstract class RouteDefinition {
   final String path;
   final String name;
-  const RouteDefinition({this.path, this.name});
+  final bool useAsDefault;
+  const RouteDefinition({this.path, this.name, this.useAsDefault : false});
 }
diff --git a/modules/angular2/src/router/route_definition.ts b/modules/angular2/src/router/route_definition.ts
index ee1266143d..7d38690ee1 100644
--- a/modules/angular2/src/router/route_definition.ts
+++ b/modules/angular2/src/router/route_definition.ts
@@ -16,10 +16,11 @@ export interface RouteDefinition {
   aux?: string;
   component?: Type | ComponentDefinition;
   loader?: Function;
-  redirectTo?: string;
+  redirectTo?: any[];
   as?: string;
   name?: string;
   data?: any;
+  useAsDefault?: boolean;
 }
 
 export interface ComponentDefinition {
diff --git a/modules/angular2/src/router/route_handler.ts b/modules/angular2/src/router/route_handler.ts
index 54ca3b2ef0..5971267ee8 100644
--- a/modules/angular2/src/router/route_handler.ts
+++ b/modules/angular2/src/router/route_handler.ts
@@ -1,8 +1,9 @@
 import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
 import {Type} from 'angular2/src/facade/lang';
+import {RouteData} from './instruction';
 
 export interface RouteHandler {
   componentType: Type;
   resolveComponentType(): Promise;
-  data?: {[key: string]: any};
+  data: RouteData;
 }
diff --git a/modules/angular2/src/router/route_recognizer.ts b/modules/angular2/src/router/route_recognizer.ts
index 6511fadda7..fb0b8973e9 100644
--- a/modules/angular2/src/router/route_recognizer.ts
+++ b/modules/angular2/src/router/route_recognizer.ts
@@ -1,184 +1,119 @@
-import {
-  RegExp,
-  RegExpWrapper,
-  isBlank,
-  isPresent,
-  isType,
-  isStringMap,
-  Type
-} from 'angular2/src/facade/lang';
-import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
-import {Map, MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
+import {isPresent, isBlank} from 'angular2/src/facade/lang';
+import {BaseException} from 'angular2/src/facade/exceptions';
+import {PromiseWrapper, Promise} from 'angular2/src/facade/promise';
+import {Map} from 'angular2/src/facade/collection';
 
-import {PathRecognizer, PathMatch} from './path_recognizer';
-import {Route, AsyncRoute, AuxRoute, Redirect, RouteDefinition} from './route_config_impl';
-import {AsyncRouteHandler} from './async_route_handler';
-import {SyncRouteHandler} from './sync_route_handler';
+import {RouteHandler} from './route_handler';
 import {Url} from './url_parser';
 import {ComponentInstruction} from './instruction';
+import {PathRecognizer} from './path_recognizer';
 
 
-/**
- * `RouteRecognizer` is responsible for recognizing routes for a single component.
- * It is consumed by `RouteRegistry`, which knows how to recognize an entire hierarchy of
- * components.
- */
-export class RouteRecognizer {
-  names = new Map();
+export abstract class RouteMatch {}
 
-  // map from name to recognizer
-  auxNames = new Map();
-
-  // map from starting path to recognizer
-  auxRoutes = new Map();
-
-  // TODO: optimize this into a trie
-  matchers: PathRecognizer[] = [];
-
-  // TODO: optimize this into a trie
-  redirects: Redirector[] = [];
-
-  config(config: RouteDefinition): boolean {
-    var handler;
-
-    if (isPresent(config.name) && config.name[0].toUpperCase() != config.name[0]) {
-      var suggestedName = config.name[0].toUpperCase() + config.name.substring(1);
-      throw new BaseException(
-          `Route "${config.path}" with name "${config.name}" does not begin with an uppercase letter. Route names should be CamelCase like "${suggestedName}".`);
-    }
-
-    if (config instanceof AuxRoute) {
-      handler = new SyncRouteHandler(config.component, config.data);
-      let path = config.path.startsWith('/') ? config.path.substring(1) : config.path;
-      var recognizer = new PathRecognizer(config.path, handler);
-      this.auxRoutes.set(path, recognizer);
-      if (isPresent(config.name)) {
-        this.auxNames.set(config.name, recognizer);
-      }
-      return recognizer.terminal;
-    }
-
-    if (config instanceof Redirect) {
-      this.redirects.push(new Redirector(config.path, config.redirectTo));
-      return true;
-    }
-
-    if (config instanceof Route) {
-      handler = new SyncRouteHandler(config.component, config.data);
-    } else if (config instanceof AsyncRoute) {
-      handler = new AsyncRouteHandler(config.loader, config.data);
-    }
-    var recognizer = new PathRecognizer(config.path, handler);
-
-    this.matchers.forEach((matcher) => {
-      if (recognizer.hash == matcher.hash) {
-        throw new BaseException(
-            `Configuration '${config.path}' conflicts with existing route '${matcher.path}'`);
-      }
-    });
-
-    this.matchers.push(recognizer);
-    if (isPresent(config.name)) {
-      this.names.set(config.name, recognizer);
-    }
-    return recognizer.terminal;
-  }
+export interface AbstractRecognizer {
+  hash: string;
+  path: string;
+  recognize(beginningSegment: Url): Promise;
+  generate(params: {[key: string]: any}): ComponentInstruction;
+}
 
 
-  /**
-   * Given a URL, returns a list of `RouteMatch`es, which are partial recognitions for some route.
-   *
-   */
-  recognize(urlParse: Url): PathMatch[] {
-    var solutions = [];
-
-    urlParse = this._redirect(urlParse);
-
-    this.matchers.forEach((pathRecognizer: PathRecognizer) => {
-      var pathMatch = pathRecognizer.recognize(urlParse);
-
-      if (isPresent(pathMatch)) {
-        solutions.push(pathMatch);
-      }
-    });
-
-    return solutions;
-  }
-
-  /** @internal */
-  _redirect(urlParse: Url): Url {
-    for (var i = 0; i < this.redirects.length; i += 1) {
-      let redirector = this.redirects[i];
-      var redirectedUrl = redirector.redirect(urlParse);
-      if (isPresent(redirectedUrl)) {
-        return redirectedUrl;
-      }
-    }
-
-    return urlParse;
-  }
-
-  recognizeAuxiliary(urlParse: Url): PathMatch {
-    var pathRecognizer = this.auxRoutes.get(urlParse.path);
-    if (isBlank(pathRecognizer)) {
-      return null;
-    }
-    return pathRecognizer.recognize(urlParse);
-  }
-
-  hasRoute(name: string): boolean { return this.names.has(name); }
-
-  generate(name: string, params: any): ComponentInstruction {
-    var pathRecognizer: PathRecognizer = this.names.get(name);
-    if (isBlank(pathRecognizer)) {
-      return null;
-    }
-    return pathRecognizer.generate(params);
-  }
-
-  generateAuxiliary(name: string, params: any): ComponentInstruction {
-    var pathRecognizer: PathRecognizer = this.auxNames.get(name);
-    if (isBlank(pathRecognizer)) {
-      return null;
-    }
-    return pathRecognizer.generate(params);
+export class PathMatch extends RouteMatch {
+  constructor(public instruction: ComponentInstruction, public remaining: Url,
+              public remainingAux: Url[]) {
+    super();
   }
 }
 
-export class Redirector {
-  segments: string[] = [];
-  toSegments: string[] = [];
 
-  constructor(path: string, redirectTo: string) {
-    if (path.startsWith('/')) {
-      path = path.substring(1);
-    }
-    this.segments = path.split('/');
-    if (redirectTo.startsWith('/')) {
-      redirectTo = redirectTo.substring(1);
-    }
-    this.toSegments = redirectTo.split('/');
+export class RedirectMatch extends RouteMatch {
+  constructor(public redirectTo: any[], public specificity) { super(); }
+}
+
+export class RedirectRecognizer implements AbstractRecognizer {
+  private _pathRecognizer: PathRecognizer;
+  public hash: string;
+
+  constructor(public path: string, public redirectTo: any[]) {
+    this._pathRecognizer = new PathRecognizer(path);
+    this.hash = this._pathRecognizer.hash;
   }
 
   /**
    * Returns `null` or a `ParsedUrl` representing the new path to match
    */
-  redirect(urlParse: Url): Url {
-    for (var i = 0; i < this.segments.length; i += 1) {
-      if (isBlank(urlParse)) {
-        return null;
-      }
-      let segment = this.segments[i];
-      if (segment != urlParse.path) {
-        return null;
-      }
-      urlParse = urlParse.child;
+  recognize(beginningSegment: Url): Promise {
+    var match = null;
+    if (isPresent(this._pathRecognizer.recognize(beginningSegment))) {
+      match = new RedirectMatch(this.redirectTo, this._pathRecognizer.specificity);
     }
+    return PromiseWrapper.resolve(match);
+  }
 
-    for (var i = this.toSegments.length - 1; i >= 0; i -= 1) {
-      let segment = this.toSegments[i];
-      urlParse = new Url(segment, urlParse);
-    }
-    return urlParse;
+  generate(params: {[key: string]: any}): ComponentInstruction {
+    throw new BaseException(`Tried to generate a redirect.`);
+  }
+}
+
+
+// represents something like '/foo/:bar'
+export class RouteRecognizer implements AbstractRecognizer {
+  specificity: number;
+  terminal: boolean = true;
+  hash: string;
+
+  private _cache: Map = new Map();
+  private _pathRecognizer: PathRecognizer;
+
+  // TODO: cache component instruction instances by params and by ParsedUrl instance
+
+  constructor(public path: string, public handler: RouteHandler) {
+    this._pathRecognizer = new PathRecognizer(path);
+    this.specificity = this._pathRecognizer.specificity;
+    this.hash = this._pathRecognizer.hash;
+    this.terminal = this._pathRecognizer.terminal;
+  }
+
+  recognize(beginningSegment: Url): Promise {
+    var res = this._pathRecognizer.recognize(beginningSegment);
+    if (isBlank(res)) {
+      return null;
+    }
+
+    return this.handler.resolveComponentType().then((_) => {
+      var componentInstruction =
+          this._getInstruction(res['urlPath'], res['urlParams'], res['allParams']);
+      return new PathMatch(componentInstruction, res['nextSegment'], res['auxiliary']);
+    });
+  }
+
+  generate(params: {[key: string]: any}): ComponentInstruction {
+    var generated = this._pathRecognizer.generate(params);
+    var urlPath = generated['urlPath'];
+    var urlParams = generated['urlParams'];
+    return this._getInstruction(urlPath, urlParams, params);
+  }
+
+  generateComponentPathValues(params: {[key: string]: any}): {[key: string]: any} {
+    return this._pathRecognizer.generate(params);
+  }
+
+  private _getInstruction(urlPath: string, urlParams: string[],
+                          params: {[key: string]: any}): ComponentInstruction {
+    if (isBlank(this.handler.componentType)) {
+      throw new BaseException(`Tried to get instruction before the type was loaded.`);
+    }
+
+    var hashKey = urlPath + '?' + urlParams.join('?');
+    if (this._cache.has(hashKey)) {
+      return this._cache.get(hashKey);
+    }
+    var instruction =
+        new ComponentInstruction(urlPath, urlParams, this.handler.data, this.handler.componentType,
+                                 this.terminal, this.specificity, params);
+    this._cache.set(hashKey, instruction);
+
+    return instruction;
   }
 }
diff --git a/modules/angular2/src/router/route_registry.ts b/modules/angular2/src/router/route_registry.ts
index cb57d6408b..7ea7c41daa 100644
--- a/modules/angular2/src/router/route_registry.ts
+++ b/modules/angular2/src/router/route_registry.ts
@@ -1,6 +1,3 @@
-import {PathMatch} from './path_recognizer';
-import {RouteRecognizer} from './route_recognizer';
-import {Instruction, ComponentInstruction, PrimaryInstruction} from './instruction';
 import {ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
 import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
 import {
@@ -10,12 +7,14 @@ import {
   isType,
   isString,
   isStringMap,
-  isFunction,
-  StringWrapper,
   Type,
-  getTypeNameForDebugging
+  getTypeNameForDebugging,
+  CONST_EXPR
 } from 'angular2/src/facade/lang';
 import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
+import {reflector} from 'angular2/src/core/reflection/reflection';
+import {Injectable, Inject, OpaqueToken} from 'angular2/core';
+
 import {
   RouteConfig,
   AsyncRoute,
@@ -24,13 +23,52 @@ import {
   Redirect,
   RouteDefinition
 } from './route_config_impl';
-import {reflector} from 'angular2/src/core/reflection/reflection';
-import {Injectable} from 'angular2/core';
+import {PathMatch, RedirectMatch, RouteMatch} from './route_recognizer';
+import {ComponentRecognizer} from './component_recognizer';
+import {
+  Instruction,
+  ResolvedInstruction,
+  RedirectInstruction,
+  UnresolvedInstruction,
+  DefaultInstruction
+} from './instruction';
+
 import {normalizeRouteConfig, assertComponentExists} from './route_config_nomalizer';
 import {parser, Url, pathSegmentsToUrl} from './url_parser';
 
 var _resolveToNull = PromiseWrapper.resolve(null);
 
+
+
+/**
+ * Token used to bind the component with the top-level {@link RouteConfig}s for the
+ * application.
+ *
+ * ### Example ([live demo](http://plnkr.co/edit/iRUP8B5OUbxCWQ3AcIDm))
+ *
+ * ```
+ * import {Component} from 'angular2/angular2';
+ * import {
+ *   ROUTER_DIRECTIVES,
+ *   ROUTER_PROVIDERS,
+ *   RouteConfig
+ * } from 'angular2/router';
+ *
+ * @Component({directives: [ROUTER_DIRECTIVES]})
+ * @RouteConfig([
+ *  {...},
+ * ])
+ * class AppCmp {
+ *   // ...
+ * }
+ *
+ * bootstrap(AppCmp, [ROUTER_PROVIDERS]);
+ * ```
+ */
+export const ROUTER_PRIMARY_COMPONENT: OpaqueToken =
+    CONST_EXPR(new OpaqueToken('RouterPrimaryComponent'));
+
+
 /**
  * The RouteRegistry holds route configurations for each component in an Angular app.
  * It is responsible for creating Instructions from URLs, and generating URLs based on route and
@@ -38,13 +76,15 @@ var _resolveToNull = PromiseWrapper.resolve(null);
  */
 @Injectable()
 export class RouteRegistry {
-  private _rules = new Map();
+  private _rules = new Map();
+
+  constructor(@Inject(ROUTER_PRIMARY_COMPONENT) private _rootComponent: Type) {}
 
   /**
    * Given a component and a configuration object, add the route to this registry
    */
   config(parentComponent: any, config: RouteDefinition): void {
-    config = normalizeRouteConfig(config);
+    config = normalizeRouteConfig(config, this);
 
     // this is here because Dart type guard reasons
     if (config instanceof Route) {
@@ -53,10 +93,10 @@ export class RouteRegistry {
       assertComponentExists(config.component, config.path);
     }
 
-    var recognizer: RouteRecognizer = this._rules.get(parentComponent);
+    var recognizer: ComponentRecognizer = this._rules.get(parentComponent);
 
     if (isBlank(recognizer)) {
-      recognizer = new RouteRecognizer();
+      recognizer = new ComponentRecognizer();
       this._rules.set(parentComponent, recognizer);
     }
 
@@ -102,102 +142,188 @@ export class RouteRegistry {
    * Given a URL and a parent component, return the most specific instruction for navigating
    * the application into the state specified by the url
    */
-  recognize(url: string, parentComponent: any): Promise {
+  recognize(url: string, ancestorInstructions: Instruction[]): Promise {
     var parsedUrl = parser.parse(url);
-    return this._recognize(parsedUrl, parentComponent);
+    return this._recognize(parsedUrl, ancestorInstructions);
   }
 
-  private _recognize(parsedUrl: Url, parentComponent): Promise {
-    return this._recognizePrimaryRoute(parsedUrl, parentComponent)
-        .then((instruction: PrimaryInstruction) =>
-                  this._completeAuxiliaryRouteMatches(instruction, parentComponent));
-  }
 
-  private _recognizePrimaryRoute(parsedUrl: Url, parentComponent): Promise {
+  /**
+   * Recognizes all parent-child routes, but creates unresolved auxiliary routes
+   */
+
+  private _recognize(parsedUrl: Url, ancestorInstructions: Instruction[],
+                     _aux = false): Promise {
+    var parentComponent =
+        ancestorInstructions.length > 0 ?
+            ancestorInstructions[ancestorInstructions.length - 1].component.componentType :
+            this._rootComponent;
+
     var componentRecognizer = this._rules.get(parentComponent);
     if (isBlank(componentRecognizer)) {
       return _resolveToNull;
     }
 
     // Matches some beginning part of the given URL
-    var possibleMatches = componentRecognizer.recognize(parsedUrl);
+    var possibleMatches: Promise[] =
+        _aux ? componentRecognizer.recognizeAuxiliary(parsedUrl) :
+               componentRecognizer.recognize(parsedUrl);
 
-    var matchPromises =
-        possibleMatches.map(candidate => this._completePrimaryRouteMatch(candidate));
+    var matchPromises: Promise[] = possibleMatches.map(
+        (candidate: Promise) => candidate.then((candidate: RouteMatch) => {
+
+          if (candidate instanceof PathMatch) {
+            var auxParentInstructions =
+                ancestorInstructions.length > 0 ?
+                    [ancestorInstructions[ancestorInstructions.length - 1]] :
+                    [];
+            var auxInstructions =
+                this._auxRoutesToUnresolved(candidate.remainingAux, auxParentInstructions);
+            var instruction = new ResolvedInstruction(candidate.instruction, null, auxInstructions);
+
+            if (candidate.instruction.terminal) {
+              return instruction;
+            }
+
+            var newAncestorComponents = ancestorInstructions.concat([instruction]);
+
+            return this._recognize(candidate.remaining, newAncestorComponents)
+                .then((childInstruction) => {
+                  if (isBlank(childInstruction)) {
+                    return null;
+                  }
+
+                  // redirect instructions are already absolute
+                  if (childInstruction instanceof RedirectInstruction) {
+                    return childInstruction;
+                  }
+                  instruction.child = childInstruction;
+                  return instruction;
+                });
+          }
+
+          if (candidate instanceof RedirectMatch) {
+            var instruction = this.generate(candidate.redirectTo, ancestorInstructions);
+            return new RedirectInstruction(instruction.component, instruction.child,
+                                           instruction.auxInstruction);
+          }
+        }));
+
+    if ((isBlank(parsedUrl) || parsedUrl.path == '') && possibleMatches.length == 0) {
+      return PromiseWrapper.resolve(this.generateDefault(parentComponent));
+    }
 
     return PromiseWrapper.all(matchPromises).then(mostSpecific);
   }
 
-  private _completePrimaryRouteMatch(partialMatch: PathMatch): Promise {
-    var instruction = partialMatch.instruction;
-    return instruction.resolveComponentType().then((componentType) => {
-      this.configFromComponent(componentType);
+  private _auxRoutesToUnresolved(auxRoutes: Url[],
+                                 parentInstructions: Instruction[]): {[key: string]: Instruction} {
+    var unresolvedAuxInstructions: {[key: string]: Instruction} = {};
 
-      if (instruction.terminal) {
-        return new PrimaryInstruction(instruction, null, partialMatch.remainingAux);
-      }
-
-      return this._recognizePrimaryRoute(partialMatch.remaining, componentType)
-          .then((childInstruction) => {
-            if (isBlank(childInstruction)) {
-              return null;
-            } else {
-              return new PrimaryInstruction(instruction, childInstruction,
-                                            partialMatch.remainingAux);
-            }
-          });
+    auxRoutes.forEach((auxUrl: Url) => {
+      unresolvedAuxInstructions[auxUrl.path] = new UnresolvedInstruction(
+          () => { return this._recognize(auxUrl, parentInstructions, true); });
     });
+
+    return unresolvedAuxInstructions;
   }
 
 
-  private _completeAuxiliaryRouteMatches(instruction: PrimaryInstruction,
-                                         parentComponent: any): Promise {
-    if (isBlank(instruction)) {
-      return _resolveToNull;
-    }
-
-    var componentRecognizer = this._rules.get(parentComponent);
-    var auxInstructions: {[key: string]: Instruction} = {};
-
-    var promises = instruction.auxUrls.map((auxSegment: Url) => {
-      var match = componentRecognizer.recognizeAuxiliary(auxSegment);
-      if (isBlank(match)) {
-        return _resolveToNull;
-      }
-      return this._completePrimaryRouteMatch(match).then((auxInstruction: PrimaryInstruction) => {
-        if (isPresent(auxInstruction)) {
-          return this._completeAuxiliaryRouteMatches(auxInstruction, parentComponent)
-              .then((finishedAuxRoute: Instruction) => {
-                auxInstructions[auxSegment.path] = finishedAuxRoute;
-              });
-        }
-      });
-    });
-    return PromiseWrapper.all(promises).then((_) => {
-      if (isBlank(instruction.child)) {
-        return new Instruction(instruction.component, null, auxInstructions);
-      }
-      return this._completeAuxiliaryRouteMatches(instruction.child,
-                                                 instruction.component.componentType)
-          .then((completeChild) => {
-            return new Instruction(instruction.component, completeChild, auxInstructions);
-          });
-    });
-  }
-
   /**
    * Given a normalized list with component names and params like: `['user', {id: 3 }]`
    * generates a url with a leading slash relative to the provided `parentComponent`.
+   *
+   * If the optional param `_aux` is `true`, then we generate starting at an auxiliary
+   * route boundary.
    */
-  generate(linkParams: any[], parentComponent: any, _aux = false): Instruction {
+  generate(linkParams: any[], ancestorInstructions: Instruction[], _aux = false): Instruction {
+    let normalizedLinkParams = splitAndFlattenLinkParams(linkParams);
+
+    var first = ListWrapper.first(normalizedLinkParams);
+    var rest = ListWrapper.slice(normalizedLinkParams, 1);
+
+    // The first segment should be either '.' (generate from parent) or '' (generate from root).
+    // When we normalize above, we strip all the slashes, './' becomes '.' and '/' becomes ''.
+    if (first == '') {
+      ancestorInstructions = [];
+    } else if (first == '..') {
+      // we already captured the first instance of "..", so we need to pop off an ancestor
+      ancestorInstructions.pop();
+      while (ListWrapper.first(rest) == '..') {
+        rest = ListWrapper.slice(rest, 1);
+        ancestorInstructions.pop();
+        if (ancestorInstructions.length <= 0) {
+          throw new BaseException(
+              `Link "${ListWrapper.toJSON(linkParams)}" has too many "../" segments.`);
+        }
+      }
+    } else if (first != '.') {
+      let parentComponent = this._rootComponent;
+      let grandparentComponent = null;
+      if (ancestorInstructions.length > 1) {
+        parentComponent =
+            ancestorInstructions[ancestorInstructions.length - 1].component.componentType;
+        grandparentComponent =
+            ancestorInstructions[ancestorInstructions.length - 2].component.componentType;
+      } else if (ancestorInstructions.length == 1) {
+        parentComponent = ancestorInstructions[0].component.componentType;
+        grandparentComponent = this._rootComponent;
+      }
+
+      // For a link with no leading `./`, `/`, or `../`, we look for a sibling and child.
+      // If both exist, we throw. Otherwise, we prefer whichever exists.
+      var childRouteExists = this.hasRoute(first, parentComponent);
+      var parentRouteExists =
+          isPresent(grandparentComponent) && this.hasRoute(first, grandparentComponent);
+
+      if (parentRouteExists && childRouteExists) {
+        let msg =
+            `Link "${ListWrapper.toJSON(linkParams)}" is ambiguous, use "./" or "../" to disambiguate.`;
+        throw new BaseException(msg);
+      }
+      if (parentRouteExists) {
+        ancestorInstructions.pop();
+      }
+      rest = linkParams;
+    }
+
+    if (rest[rest.length - 1] == '') {
+      rest.pop();
+    }
+
+    if (rest.length < 1) {
+      let msg = `Link "${ListWrapper.toJSON(linkParams)}" must include a route name.`;
+      throw new BaseException(msg);
+    }
+
+    var generatedInstruction = this._generate(rest, ancestorInstructions, _aux);
+
+    for (var i = ancestorInstructions.length - 1; i >= 0; i--) {
+      let ancestorInstruction = ancestorInstructions[i];
+      generatedInstruction = ancestorInstruction.replaceChild(generatedInstruction);
+    }
+
+    return generatedInstruction;
+  }
+
+
+  /*
+   * Internal helper that does not make any assertions about the beginning of the link DSL
+   */
+  private _generate(linkParams: any[], ancestorInstructions: Instruction[],
+                    _aux = false): Instruction {
+    let parentComponent =
+        ancestorInstructions.length > 0 ?
+            ancestorInstructions[ancestorInstructions.length - 1].component.componentType :
+            this._rootComponent;
+
+
+    if (linkParams.length == 0) {
+      return this.generateDefault(parentComponent);
+    }
     let linkIndex = 0;
     let routeName = linkParams[linkIndex];
 
-    // TODO: this is kind of odd but it makes existing assertions pass
-    if (isBlank(parentComponent)) {
-      throw new BaseException(`Could not find route named "${routeName}".`);
-    }
-
     if (!isString(routeName)) {
       throw new BaseException(`Unexpected segment "${routeName}" in link DSL. Expected a string.`);
     } else if (routeName == '' || routeName == '.' || routeName == '..') {
@@ -216,7 +342,13 @@ export class RouteRegistry {
     let auxInstructions: {[key: string]: Instruction} = {};
     var nextSegment;
     while (linkIndex + 1 < linkParams.length && isArray(nextSegment = linkParams[linkIndex + 1])) {
-      auxInstructions[nextSegment[0]] = this.generate(nextSegment, parentComponent, true);
+      let auxParentInstruction = ancestorInstructions.length > 0 ?
+                                     [ancestorInstructions[ancestorInstructions.length - 1]] :
+                                     [];
+      let auxInstruction = this._generate(nextSegment, auxParentInstruction, true);
+
+      // TODO: this will not work for aux routes with parameters or multiple segments
+      auxInstructions[auxInstruction.component.urlPath] = auxInstruction;
       linkIndex += 1;
     }
 
@@ -226,74 +358,107 @@ export class RouteRegistry {
           `Component "${getTypeNameForDebugging(parentComponent)}" has no route config.`);
     }
 
-    var componentInstruction = _aux ? componentRecognizer.generateAuxiliary(routeName, params) :
-                                      componentRecognizer.generate(routeName, params);
+    var routeRecognizer =
+        (_aux ? componentRecognizer.auxNames : componentRecognizer.names).get(routeName);
 
-    if (isBlank(componentInstruction)) {
+    if (!isPresent(routeRecognizer)) {
       throw new BaseException(
           `Component "${getTypeNameForDebugging(parentComponent)}" has no route named "${routeName}".`);
     }
 
-    var childInstruction = null;
-    if (linkIndex + 1 < linkParams.length) {
-      var remaining = linkParams.slice(linkIndex + 1);
-      childInstruction = this.generate(remaining, componentInstruction.componentType);
-    } else if (!componentInstruction.terminal) {
-      throw new BaseException(
-          `Link "${ListWrapper.toJSON(linkParams)}" does not resolve to a terminal or async instruction.`);
+    if (!isPresent(routeRecognizer.handler.componentType)) {
+      var compInstruction = routeRecognizer.generateComponentPathValues(params);
+      return new UnresolvedInstruction(() => {
+        return routeRecognizer.handler.resolveComponentType().then(
+            (_) => { return this._generate(linkParams, ancestorInstructions, _aux); });
+      }, compInstruction['urlPath'], compInstruction['urlParams']);
     }
 
-    return new Instruction(componentInstruction, childInstruction, auxInstructions);
+    var componentInstruction = _aux ? componentRecognizer.generateAuxiliary(routeName, params) :
+                                      componentRecognizer.generate(routeName, params);
+
+
+
+    var remaining = linkParams.slice(linkIndex + 1);
+
+    var instruction = new ResolvedInstruction(componentInstruction, null, auxInstructions);
+
+    // the component is sync
+    if (isPresent(componentInstruction.componentType)) {
+      let childInstruction: Instruction = null;
+      if (linkIndex + 1 < linkParams.length) {
+        let childAncestorComponents = ancestorInstructions.concat([instruction]);
+        childInstruction = this._generate(remaining, childAncestorComponents);
+      } else if (!componentInstruction.terminal) {
+        // ... look for defaults
+        childInstruction = this.generateDefault(componentInstruction.componentType);
+
+        if (isBlank(childInstruction)) {
+          throw new BaseException(
+              `Link "${ListWrapper.toJSON(linkParams)}" does not resolve to a terminal instruction.`);
+        }
+      }
+      instruction.child = childInstruction;
+    }
+
+    return instruction;
   }
 
   public hasRoute(name: string, parentComponent: any): boolean {
-    var componentRecognizer: RouteRecognizer = this._rules.get(parentComponent);
+    var componentRecognizer: ComponentRecognizer = this._rules.get(parentComponent);
     if (isBlank(componentRecognizer)) {
       return false;
     }
     return componentRecognizer.hasRoute(name);
   }
 
-  // if the child includes a redirect like : "/" -> "/something",
-  // we want to honor that redirection when creating the link
-  private _generateRedirects(componentCursor: Type): Instruction {
+  public generateDefault(componentCursor: Type): Instruction {
     if (isBlank(componentCursor)) {
       return null;
     }
+
     var componentRecognizer = this._rules.get(componentCursor);
-    if (isBlank(componentRecognizer)) {
+    if (isBlank(componentRecognizer) || isBlank(componentRecognizer.defaultRoute)) {
       return null;
     }
 
-    for (let i = 0; i < componentRecognizer.redirects.length; i += 1) {
-      let redirect = componentRecognizer.redirects[i];
 
-      // we only handle redirecting from an empty segment
-      if (redirect.segments.length == 1 && redirect.segments[0] == '') {
-        var toSegments = pathSegmentsToUrl(redirect.toSegments);
-        var matches = componentRecognizer.recognize(toSegments);
-        var primaryInstruction =
-            ListWrapper.maximum(matches, (match: PathMatch) => match.instruction.specificity);
-
-        if (isPresent(primaryInstruction)) {
-          var child = this._generateRedirects(primaryInstruction.instruction.componentType);
-          return new Instruction(primaryInstruction.instruction, child, {});
-        }
-        return null;
+    var defaultChild = null;
+    if (isPresent(componentRecognizer.defaultRoute.handler.componentType)) {
+      var componentInstruction = componentRecognizer.defaultRoute.generate({});
+      if (!componentRecognizer.defaultRoute.terminal) {
+        defaultChild = this.generateDefault(componentRecognizer.defaultRoute.handler.componentType);
       }
+      return new DefaultInstruction(componentInstruction, defaultChild);
     }
 
-    return null;
+    return new UnresolvedInstruction(() => {
+      return componentRecognizer.defaultRoute.handler.resolveComponentType().then(
+          () => this.generateDefault(componentCursor));
+    });
   }
 }
 
+/*
+ * Given: ['/a/b', {c: 2}]
+ * Returns: ['', 'a', 'b', {c: 2}]
+ */
+function splitAndFlattenLinkParams(linkParams: any[]): any[] {
+  return linkParams.reduce((accumulation: any[], item) => {
+    if (isString(item)) {
+      let strItem: string = item;
+      return accumulation.concat(strItem.split('/'));
+    }
+    accumulation.push(item);
+    return accumulation;
+  }, []);
+}
 
 /*
  * Given a list of instructions, returns the most specific instruction
  */
-function mostSpecific(instructions: PrimaryInstruction[]): PrimaryInstruction {
-  return ListWrapper.maximum(
-      instructions, (instruction: PrimaryInstruction) => instruction.component.specificity);
+function mostSpecific(instructions: Instruction[]): Instruction {
+  return ListWrapper.maximum(instructions, (instruction: Instruction) => instruction.specificity);
 }
 
 function assertTerminalComponent(component, path) {
diff --git a/modules/angular2/src/router/router.ts b/modules/angular2/src/router/router.ts
index 5eb4a31ec9..137ac7db36 100644
--- a/modules/angular2/src/router/router.ts
+++ b/modules/angular2/src/router/router.ts
@@ -2,13 +2,12 @@ import {Promise, PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2
 import {Map, StringMapWrapper, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
 import {isBlank, isString, isPresent, Type, isArray} from 'angular2/src/facade/lang';
 import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
-import {RouteRegistry} from './route_registry';
+import {Inject, Injectable} from 'angular2/core';
+
+import {RouteRegistry, ROUTER_PRIMARY_COMPONENT} from './route_registry';
 import {
   ComponentInstruction,
   Instruction,
-  stringifyInstruction,
-  stringifyInstructionPath,
-  stringifyInstructionQuery
 } from './instruction';
 import {RouterOutlet} from './router_outlet';
 import {Location} from './location';
@@ -126,6 +125,7 @@ export class Router {
            this._currentInstruction.component == instruction.component;
   }
 
+
   /**
    * Dynamically update the routing configuration and trigger a navigation.
    *
@@ -144,6 +144,7 @@ export class Router {
     return this.renavigate();
   }
 
+
   /**
    * Navigate based on the provided Route Link DSL. It's preferred to navigate with this method
    * over `navigateByUrl`.
@@ -212,7 +213,7 @@ export class Router {
                 if (result) {
                   return this.commit(instruction, _skipLocationChange)
                       .then((_) => {
-                        this._emitNavigationFinish(stringifyInstruction(instruction));
+                        this._emitNavigationFinish(instruction.toRootUrl());
                         return true;
                       });
                 }
@@ -220,25 +221,22 @@ export class Router {
         });
   }
 
-  // TODO(btford): it'd be nice to remove this method as part of cleaning up the traversal logic
-  // Since refactoring `Router.generate` to return an instruction rather than a string, it's not
-  // guaranteed that the `componentType`s for the terminal async routes have been loaded by the time
-  // we begin navigation. The method below simply traverses instructions and resolves any components
-  // for which `componentType` is not present
   /** @internal */
   _settleInstruction(instruction: Instruction): Promise {
-    var unsettledInstructions: Array> = [];
-    if (isBlank(instruction.component.componentType)) {
-      unsettledInstructions.push(instruction.component.resolveComponentType().then(
-          (type: Type) => { this.registry.configFromComponent(type); }));
-    }
-    if (isPresent(instruction.child)) {
-      unsettledInstructions.push(this._settleInstruction(instruction.child));
-    }
-    StringMapWrapper.forEach(instruction.auxInstruction, (instruction, _) => {
-      unsettledInstructions.push(this._settleInstruction(instruction));
+    return instruction.resolveComponent().then((_) => {
+      instruction.component.reuse = false;
+
+      var unsettledInstructions: Array> = [];
+
+      if (isPresent(instruction.child)) {
+        unsettledInstructions.push(this._settleInstruction(instruction.child));
+      }
+
+      StringMapWrapper.forEach(instruction.auxInstruction, (instruction, _) => {
+        unsettledInstructions.push(this._settleInstruction(instruction));
+      });
+      return PromiseWrapper.all(unsettledInstructions);
     });
-    return PromiseWrapper.all(unsettledInstructions);
   }
 
   private _emitNavigationFinish(url): void { ObservableWrapper.callEmit(this._subject, url); }
@@ -378,7 +376,20 @@ export class Router {
    * Given a URL, returns an instruction representing the component graph
    */
   recognize(url: string): Promise {
-    return this.registry.recognize(url, this.hostComponent);
+    var ancestorComponents = this._getAncestorInstructions();
+    return this.registry.recognize(url, ancestorComponents);
+  }
+
+  private _getAncestorInstructions(): Instruction[] {
+    var ancestorComponents = [];
+    var ancestorRouter = this;
+    while (isPresent(ancestorRouter.parent) &&
+           isPresent(ancestorRouter.parent._currentInstruction)) {
+      ancestorRouter = ancestorRouter.parent;
+      ancestorComponents.unshift(ancestorRouter._currentInstruction);
+    }
+
+    return ancestorComponents;
   }
 
 
@@ -399,80 +410,20 @@ export class Router {
    * app's base href.
    */
   generate(linkParams: any[]): Instruction {
-    let normalizedLinkParams = splitAndFlattenLinkParams(linkParams);
-
-    var first = ListWrapper.first(normalizedLinkParams);
-    var rest = ListWrapper.slice(normalizedLinkParams, 1);
-
-    var router = this;
-
-    // The first segment should be either '.' (generate from parent) or '' (generate from root).
-    // When we normalize above, we strip all the slashes, './' becomes '.' and '/' becomes ''.
-    if (first == '') {
-      while (isPresent(router.parent)) {
-        router = router.parent;
-      }
-    } else if (first == '..') {
-      router = router.parent;
-      while (ListWrapper.first(rest) == '..') {
-        rest = ListWrapper.slice(rest, 1);
-        router = router.parent;
-        if (isBlank(router)) {
-          throw new BaseException(
-              `Link "${ListWrapper.toJSON(linkParams)}" has too many "../" segments.`);
-        }
-      }
-    } else if (first != '.') {
-      // For a link with no leading `./`, `/`, or `../`, we look for a sibling and child.
-      // If both exist, we throw. Otherwise, we prefer whichever exists.
-      var childRouteExists = this.registry.hasRoute(first, this.hostComponent);
-      var parentRouteExists =
-          isPresent(this.parent) && this.registry.hasRoute(first, this.parent.hostComponent);
-
-      if (parentRouteExists && childRouteExists) {
-        let msg =
-            `Link "${ListWrapper.toJSON(linkParams)}" is ambiguous, use "./" or "../" to disambiguate.`;
-        throw new BaseException(msg);
-      }
-      if (parentRouteExists) {
-        router = this.parent;
-      }
-      rest = linkParams;
-    }
-
-    if (rest[rest.length - 1] == '') {
-      rest.pop();
-    }
-
-    if (rest.length < 1) {
-      let msg = `Link "${ListWrapper.toJSON(linkParams)}" must include a route name.`;
-      throw new BaseException(msg);
-    }
-
-    var nextInstruction = this.registry.generate(rest, router.hostComponent);
-
-    var url = [];
-    var parent = router.parent;
-    while (isPresent(parent)) {
-      url.unshift(parent._currentInstruction);
-      parent = parent.parent;
-    }
-
-    while (url.length > 0) {
-      nextInstruction = url.pop().replaceChild(nextInstruction);
-    }
-
-    return nextInstruction;
+    var ancestorInstructions = this._getAncestorInstructions();
+    return this.registry.generate(linkParams, ancestorInstructions);
   }
 }
 
+@Injectable()
 export class RootRouter extends Router {
   /** @internal */
   _location: Location;
   /** @internal */
   _locationSub: Object;
 
-  constructor(registry: RouteRegistry, location: Location, primaryComponent: Type) {
+  constructor(registry: RouteRegistry, location: Location,
+              @Inject(ROUTER_PRIMARY_COMPONENT) primaryComponent: Type) {
     super(registry, null, primaryComponent);
     this._location = location;
     this._locationSub = this._location.subscribe(
@@ -482,8 +433,8 @@ export class RootRouter extends Router {
   }
 
   commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise {
-    var emitPath = stringifyInstructionPath(instruction);
-    var emitQuery = stringifyInstructionQuery(instruction);
+    var emitPath = instruction.toUrlPath();
+    var emitQuery = instruction.toUrlQuery();
     if (emitPath.length > 0) {
       emitPath = '/' + emitPath;
     }
@@ -521,20 +472,6 @@ class ChildRouter extends Router {
   }
 }
 
-/*
- * Given: ['/a/b', {c: 2}]
- * Returns: ['', 'a', 'b', {c: 2}]
- */
-function splitAndFlattenLinkParams(linkParams: any[]): any[] {
-  return linkParams.reduce((accumulation: any[], item) => {
-    if (isString(item)) {
-      let strItem: string = item;
-      return accumulation.concat(strItem.split('/'));
-    }
-    accumulation.push(item);
-    return accumulation;
-  }, []);
-}
 
 function canActivateOne(nextInstruction: Instruction,
                         prevInstruction: Instruction): Promise {
diff --git a/modules/angular2/src/router/router_link.ts b/modules/angular2/src/router/router_link.ts
index 6ea1e5d6bb..6d380f4ffd 100644
--- a/modules/angular2/src/router/router_link.ts
+++ b/modules/angular2/src/router/router_link.ts
@@ -3,7 +3,7 @@ import {isString} from 'angular2/src/facade/lang';
 
 import {Router} from './router';
 import {Location} from './location';
-import {Instruction, stringifyInstruction} from './instruction';
+import {Instruction} from './instruction';
 
 /**
  * The RouterLink directive lets you link to specific parts of your app.
@@ -61,7 +61,7 @@ export class RouterLink {
     this._routeParams = changes;
     this._navigationInstruction = this._router.generate(this._routeParams);
 
-    var navigationHref = stringifyInstruction(this._navigationInstruction);
+    var navigationHref = this._navigationInstruction.toLinkUrl();
     this.visibleHref = this._location.prepareExternalUrl(navigationHref);
   }
 
diff --git a/modules/angular2/src/router/sync_route_handler.ts b/modules/angular2/src/router/sync_route_handler.ts
index 5ad7f0aa8d..1b951a098a 100644
--- a/modules/angular2/src/router/sync_route_handler.ts
+++ b/modules/angular2/src/router/sync_route_handler.ts
@@ -1,13 +1,19 @@
-import {RouteHandler} from './route_handler';
 import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
-import {Type} from 'angular2/src/facade/lang';
+import {isPresent, Type} from 'angular2/src/facade/lang';
+
+import {RouteHandler} from './route_handler';
+import {RouteData, BLANK_ROUTE_DATA} from './instruction';
+
 
 export class SyncRouteHandler implements RouteHandler {
+  public data: RouteData;
+
   /** @internal */
   _resolvedComponent: Promise = null;
 
-  constructor(public componentType: Type, public data?: {[key: string]: any}) {
+  constructor(public componentType: Type, data?: {[key: string]: any}) {
     this._resolvedComponent = PromiseWrapper.resolve(componentType);
+    this.data = isPresent(data) ? new RouteData(data) : BLANK_ROUTE_DATA;
   }
 
   resolveComponentType(): Promise { return this._resolvedComponent; }
diff --git a/modules/angular2/test/router/component_recognizer_spec.ts b/modules/angular2/test/router/component_recognizer_spec.ts
new file mode 100644
index 0000000000..5924bf5b73
--- /dev/null
+++ b/modules/angular2/test/router/component_recognizer_spec.ts
@@ -0,0 +1,216 @@
+import {
+  AsyncTestCompleter,
+  describe,
+  it,
+  iit,
+  ddescribe,
+  expect,
+  inject,
+  beforeEach,
+  SpyObject
+} from 'angular2/testing_internal';
+
+import {Map, StringMapWrapper} from 'angular2/src/facade/collection';
+
+import {RouteMatch, PathMatch, RedirectMatch} from 'angular2/src/router/route_recognizer';
+import {ComponentRecognizer} from 'angular2/src/router/component_recognizer';
+
+import {Route, Redirect} from 'angular2/src/router/route_config_decorator';
+import {parser} from 'angular2/src/router/url_parser';
+import {Promise, PromiseWrapper} from 'angular2/src/facade/promise';
+
+
+export function main() {
+  describe('ComponentRecognizer', () => {
+    var recognizer: ComponentRecognizer;
+
+    beforeEach(() => { recognizer = new ComponentRecognizer(); });
+
+
+    it('should recognize a static segment', inject([AsyncTestCompleter], (async) => {
+         recognizer.config(new Route({path: '/test', component: DummyCmpA}));
+         recognize(recognizer, '/test')
+             .then((solutions: RouteMatch[]) => {
+               expect(solutions.length).toBe(1);
+               expect(getComponentType(solutions[0])).toEqual(DummyCmpA);
+               async.done();
+             });
+       }));
+
+
+    it('should recognize a single slash', inject([AsyncTestCompleter], (async) => {
+         recognizer.config(new Route({path: '/', component: DummyCmpA}));
+         recognize(recognizer, '/')
+             .then((solutions: RouteMatch[]) => {
+               expect(solutions.length).toBe(1);
+               expect(getComponentType(solutions[0])).toEqual(DummyCmpA);
+               async.done();
+             });
+       }));
+
+
+    it('should recognize a dynamic segment', inject([AsyncTestCompleter], (async) => {
+         recognizer.config(new Route({path: '/user/:name', component: DummyCmpA}));
+         recognize(recognizer, '/user/brian')
+             .then((solutions: RouteMatch[]) => {
+               expect(solutions.length).toBe(1);
+               expect(getComponentType(solutions[0])).toEqual(DummyCmpA);
+               expect(getParams(solutions[0])).toEqual({'name': 'brian'});
+               async.done();
+             });
+       }));
+
+
+    it('should recognize a star segment', inject([AsyncTestCompleter], (async) => {
+         recognizer.config(new Route({path: '/first/*rest', component: DummyCmpA}));
+         recognize(recognizer, '/first/second/third')
+             .then((solutions: RouteMatch[]) => {
+               expect(solutions.length).toBe(1);
+               expect(getComponentType(solutions[0])).toEqual(DummyCmpA);
+               expect(getParams(solutions[0])).toEqual({'rest': 'second/third'});
+               async.done();
+             });
+       }));
+
+
+    it('should throw when given two routes that start with the same static segment', () => {
+      recognizer.config(new Route({path: '/hello', component: DummyCmpA}));
+      expect(() => recognizer.config(new Route({path: '/hello', component: DummyCmpB})))
+          .toThrowError('Configuration \'/hello\' conflicts with existing route \'/hello\'');
+    });
+
+
+    it('should throw when given two routes that have dynamic segments in the same order', () => {
+      recognizer.config(new Route({path: '/hello/:person/how/:doyoudou', component: DummyCmpA}));
+      expect(() => recognizer.config(
+                 new Route({path: '/hello/:friend/how/:areyou', component: DummyCmpA})))
+          .toThrowError(
+              'Configuration \'/hello/:friend/how/:areyou\' conflicts with existing route \'/hello/:person/how/:doyoudou\'');
+
+      expect(() => recognizer.config(
+                 new Redirect({path: '/hello/:pal/how/:goesit', redirectTo: ['/Foo']})))
+          .toThrowError(
+              'Configuration \'/hello/:pal/how/:goesit\' conflicts with existing route \'/hello/:person/how/:doyoudou\'');
+    });
+
+
+    it('should recognize redirects', inject([AsyncTestCompleter], (async) => {
+         recognizer.config(new Route({path: '/b', component: DummyCmpA}));
+         recognizer.config(new Redirect({path: '/a', redirectTo: ['B']}));
+         recognize(recognizer, '/a')
+             .then((solutions: RouteMatch[]) => {
+               expect(solutions.length).toBe(1);
+               var solution = solutions[0];
+               expect(solution).toBeAnInstanceOf(RedirectMatch);
+               if (solution instanceof RedirectMatch) {
+                 expect(solution.redirectTo).toEqual(['B']);
+               }
+               async.done();
+             });
+       }));
+
+
+    it('should generate URLs with params', () => {
+      recognizer.config(new Route({path: '/app/user/:name', component: DummyCmpA, name: 'User'}));
+      var instruction = recognizer.generate('User', {'name': 'misko'});
+      expect(instruction.urlPath).toEqual('app/user/misko');
+    });
+
+
+    it('should generate URLs with numeric params', () => {
+      recognizer.config(new Route({path: '/app/page/:number', component: DummyCmpA, name: 'Page'}));
+      expect(recognizer.generate('Page', {'number': 42}).urlPath).toEqual('app/page/42');
+    });
+
+
+    it('should throw in the absence of required params URLs', () => {
+      recognizer.config(new Route({path: 'app/user/:name', component: DummyCmpA, name: 'User'}));
+      expect(() => recognizer.generate('User', {}))
+          .toThrowError('Route generator for \'name\' was not included in parameters passed.');
+    });
+
+
+    it('should throw if the route alias is not TitleCase', () => {
+      expect(() => recognizer.config(
+                 new Route({path: 'app/user/:name', component: DummyCmpA, name: 'user'})))
+          .toThrowError(
+              `Route "app/user/:name" with name "user" does not begin with an uppercase letter. Route names should be CamelCase like "User".`);
+    });
+
+
+    describe('params', () => {
+      it('should recognize parameters within the URL path',
+         inject([AsyncTestCompleter], (async) => {
+           recognizer.config(
+               new Route({path: 'profile/:name', component: DummyCmpA, name: 'User'}));
+           recognize(recognizer, '/profile/matsko?comments=all')
+               .then((solutions: RouteMatch[]) => {
+                 expect(solutions.length).toBe(1);
+                 expect(getParams(solutions[0])).toEqual({'name': 'matsko', 'comments': 'all'});
+                 async.done();
+               });
+         }));
+
+
+      it('should generate and populate the given static-based route with querystring params',
+         () => {
+           recognizer.config(
+               new Route({path: 'forum/featured', component: DummyCmpA, name: 'ForumPage'}));
+
+           var params = {'start': 10, 'end': 100};
+
+           var result = recognizer.generate('ForumPage', params);
+           expect(result.urlPath).toEqual('forum/featured');
+           expect(result.urlParams).toEqual(['start=10', 'end=100']);
+         });
+
+
+      it('should prefer positional params over query params',
+         inject([AsyncTestCompleter], (async) => {
+           recognizer.config(
+               new Route({path: 'profile/:name', component: DummyCmpA, name: 'User'}));
+           recognize(recognizer, '/profile/yegor?name=igor')
+               .then((solutions: RouteMatch[]) => {
+                 expect(solutions.length).toBe(1);
+                 expect(getParams(solutions[0])).toEqual({'name': 'yegor'});
+                 async.done();
+               });
+         }));
+
+
+      it('should ignore matrix params for the top-level component',
+         inject([AsyncTestCompleter], (async) => {
+           recognizer.config(
+               new Route({path: '/home/:subject', component: DummyCmpA, name: 'User'}));
+           recognize(recognizer, '/home;sort=asc/zero;one=1?two=2')
+               .then((solutions: RouteMatch[]) => {
+                 expect(solutions.length).toBe(1);
+                 expect(getParams(solutions[0])).toEqual({'subject': 'zero', 'two': '2'});
+                 async.done();
+               });
+         }));
+    });
+  });
+}
+
+function recognize(recognizer: ComponentRecognizer, url: string): Promise {
+  var parsedUrl = parser.parse(url);
+  return PromiseWrapper.all(recognizer.recognize(parsedUrl));
+}
+
+function getComponentType(routeMatch: RouteMatch): any {
+  if (routeMatch instanceof PathMatch) {
+    return routeMatch.instruction.componentType;
+  }
+  return null;
+}
+
+function getParams(routeMatch: RouteMatch): any {
+  if (routeMatch instanceof PathMatch) {
+    return routeMatch.instruction.params;
+  }
+  return null;
+}
+
+class DummyCmpA {}
+class DummyCmpB {}
diff --git a/modules/angular2/test/router/integration/README.md b/modules/angular2/test/router/integration/README.md
new file mode 100644
index 0000000000..157d7423d2
--- /dev/null
+++ b/modules/angular2/test/router/integration/README.md
@@ -0,0 +1,9 @@
+# Router integration tests
+
+These tests only mock out `Location`, and otherwise use all the real parts of routing to ensure that
+various routing scenarios work as expected.
+
+The Component Router in Angular 2 exposes only a handful of different options, but because they can
+be combined and nested in so many ways, it's difficult to rigorously test all the cases.
+
+The address this problem, we introduce `describeRouter`, `describeWith`, and `describeWithout`.
\ No newline at end of file
diff --git a/modules/angular2/test/router/integration/async_route_spec.ts b/modules/angular2/test/router/integration/async_route_spec.ts
new file mode 100644
index 0000000000..40b182bfec
--- /dev/null
+++ b/modules/angular2/test/router/integration/async_route_spec.ts
@@ -0,0 +1,28 @@
+import {
+  describeRouter,
+  ddescribeRouter,
+  describeWith,
+  describeWithout,
+  describeWithAndWithout,
+  itShouldRoute
+} from './util';
+
+import {registerSpecs} from './impl/async_route_spec_impl';
+
+export function main() {
+  registerSpecs();
+
+  ddescribeRouter('async routes', () => {
+    describeWithout('children', () => {
+      describeWith('route data', itShouldRoute);
+      describeWithAndWithout('params', itShouldRoute);
+    });
+
+    describeWith('sync children',
+                 () => { describeWithAndWithout('default routes', itShouldRoute); });
+
+    describeWith('async children', () => {
+      describeWithAndWithout('params', () => { describeWithout('default routes', itShouldRoute); });
+    });
+  });
+}
diff --git a/modules/angular2/test/router/integration/auxiliary_route_spec.ts b/modules/angular2/test/router/integration/auxiliary_route_spec.ts
new file mode 100644
index 0000000000..14615bc466
--- /dev/null
+++ b/modules/angular2/test/router/integration/auxiliary_route_spec.ts
@@ -0,0 +1,145 @@
+import {
+  RootTestComponent,
+  AsyncTestCompleter,
+  TestComponentBuilder,
+  beforeEach,
+  ddescribe,
+  xdescribe,
+  describe,
+  el,
+  expect,
+  iit,
+  inject,
+  beforeEachProviders,
+  it,
+  xit
+} from 'angular2/testing_internal';
+
+import {provide, Component, Injector, Inject} from 'angular2/core';
+
+import {Router, ROUTER_DIRECTIVES, RouteParams, RouteData, Location} from 'angular2/router';
+import {RouteConfig, Route, AuxRoute, Redirect} from 'angular2/src/router/route_config_decorator';
+
+import {TEST_ROUTER_PROVIDERS, RootCmp, compile, clickOnElement, getHref} from './util';
+
+function getLinkElement(rtc: RootTestComponent) {
+  return rtc.debugElement.componentViewChildren[0].nativeElement;
+}
+
+var cmpInstanceCount;
+var childCmpInstanceCount;
+
+export function main() {
+  describe('auxiliary routes', () => {
+
+    var tcb: TestComponentBuilder;
+    var fixture: RootTestComponent;
+    var rtr;
+
+    beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+    beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+      tcb = tcBuilder;
+      rtr = router;
+      childCmpInstanceCount = 0;
+      cmpInstanceCount = 0;
+    }));
+
+    it('should recognize and navigate from the URL', inject([AsyncTestCompleter], (async) => {
+         compile(tcb, `main {} | aux {}`)
+             .then((rtc) => {fixture = rtc})
+             .then((_) => rtr.config([
+               new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
+               new AuxRoute({path: '/modal', component: ModalCmp, name: 'Aux'})
+             ]))
+             .then((_) => rtr.navigateByUrl('/hello(modal)'))
+             .then((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement).toHaveText('main {hello} | aux {modal}');
+               async.done();
+             });
+       }));
+
+    it('should navigate via the link DSL', inject([AsyncTestCompleter], (async) => {
+         compile(tcb, `main {} | aux {}`)
+             .then((rtc) => {fixture = rtc})
+             .then((_) => rtr.config([
+               new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
+               new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
+             ]))
+             .then((_) => rtr.navigate(['/Hello', ['Modal']]))
+             .then((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement).toHaveText('main {hello} | aux {modal}');
+               async.done();
+             });
+       }));
+
+    it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+         compile(
+             tcb,
+             `open modal | main {} | aux {}`)
+             .then((rtc) => {fixture = rtc})
+             .then((_) => rtr.config([
+               new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
+               new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
+             ]))
+             .then((_) => {
+               fixture.detectChanges();
+               expect(getHref(getLinkElement(fixture))).toEqual('/hello(modal)');
+               async.done();
+             });
+       }));
+
+    it('should navigate from a link click',
+       inject([AsyncTestCompleter, Location], (async, location) => {
+         compile(
+             tcb,
+             `open modal | main {} | aux {}`)
+             .then((rtc) => {fixture = rtc})
+             .then((_) => rtr.config([
+               new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
+               new AuxRoute({path: '/modal', component: ModalCmp, name: 'Modal'})
+             ]))
+             .then((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement)
+                   .toHaveText('open modal | main {} | aux {}');
+
+               rtr.subscribe((_) => {
+                 fixture.detectChanges();
+                 expect(fixture.debugElement.nativeElement)
+                     .toHaveText('open modal | main {hello} | aux {modal}');
+                 expect(location.urlChanges).toEqual(['/hello(modal)']);
+                 async.done();
+               });
+
+               clickOnElement(getLinkElement(fixture));
+             });
+       }));
+  });
+}
+
+
+@Component({selector: 'hello-cmp', template: `{{greeting}}`})
+class HelloCmp {
+  greeting: string;
+  constructor() { this.greeting = 'hello'; }
+}
+
+@Component({selector: 'modal-cmp', template: `modal`})
+class ModalCmp {
+}
+
+@Component({
+  selector: 'aux-cmp',
+  template: 'main {} | ' +
+                'aux {}',
+  directives: [ROUTER_DIRECTIVES],
+})
+@RouteConfig([
+  new Route({path: '/hello', component: HelloCmp, name: 'Hello'}),
+  new AuxRoute({path: '/modal', component: ModalCmp, name: 'Aux'})
+])
+class AuxCmp {
+}
diff --git a/modules/angular2/test/router/integration/router_integration_spec.ts b/modules/angular2/test/router/integration/bootstrap_spec.ts
similarity index 78%
rename from modules/angular2/test/router/integration/router_integration_spec.ts
rename to modules/angular2/test/router/integration/bootstrap_spec.ts
index 1f0c0c04bb..22fac7e942 100644
--- a/modules/angular2/test/router/integration/router_integration_spec.ts
+++ b/modules/angular2/test/router/integration/bootstrap_spec.ts
@@ -29,53 +29,47 @@ import {
   Router,
   APP_BASE_HREF,
   ROUTER_DIRECTIVES,
-  HashLocationStrategy
+  LocationStrategy
 } from 'angular2/router';
 
-import {LocationStrategy} from 'angular2/src/router/location_strategy';
 import {MockLocationStrategy} from 'angular2/src/mock/mock_location_strategy';
 import {ApplicationRef} from 'angular2/src/core/application_ref';
 import {MockApplicationRef} from 'angular2/src/mock/mock_application_ref';
 
 export function main() {
-  describe('router injectables', () => {
-    beforeEachProviders(() => {
-      return [
-        ROUTER_PROVIDERS,
-        provide(LocationStrategy, {useClass: MockLocationStrategy}),
-        provide(ApplicationRef, {useClass: MockApplicationRef})
-      ];
-    });
+  describe('router bootstrap', () => {
+    beforeEachProviders(() => [
+      ROUTER_PROVIDERS,
+      provide(LocationStrategy, {useClass: MockLocationStrategy}),
+      provide(ApplicationRef, {useClass: MockApplicationRef})
+    ]);
 
     // do not refactor out the `bootstrap` functionality. We still want to
     // keep this test around so we can ensure that bootstrapping a router works
-    describe('bootstrap functionality', () => {
-      it('should bootstrap a simple app', inject([AsyncTestCompleter], (async) => {
-           var fakeDoc = DOM.createHtmlDocument();
-           var el = DOM.createElement('app-cmp', fakeDoc);
-           DOM.appendChild(fakeDoc.body, el);
+    it('should bootstrap a simple app', inject([AsyncTestCompleter], (async) => {
+         var fakeDoc = DOM.createHtmlDocument();
+         var el = DOM.createElement('app-cmp', fakeDoc);
+         DOM.appendChild(fakeDoc.body, el);
 
-           bootstrap(AppCmp,
-                     [
-                       ROUTER_PROVIDERS,
-                       provide(ROUTER_PRIMARY_COMPONENT, {useValue: AppCmp}),
-                       provide(LocationStrategy, {useClass: MockLocationStrategy}),
-                       provide(DOCUMENT, {useValue: fakeDoc})
-                     ])
-               .then((applicationRef) => {
-                 var router = applicationRef.hostComponent.router;
-                 router.subscribe((_) => {
-                   expect(el).toHaveText('outer { hello }');
-                   expect(applicationRef.hostComponent.location.path()).toEqual('');
-                   async.done();
-                 });
+         bootstrap(AppCmp,
+                   [
+                     ROUTER_PROVIDERS,
+                     provide(ROUTER_PRIMARY_COMPONENT, {useValue: AppCmp}),
+                     provide(LocationStrategy, {useClass: MockLocationStrategy}),
+                     provide(DOCUMENT, {useValue: fakeDoc})
+                   ])
+             .then((applicationRef) => {
+               var router = applicationRef.hostComponent.router;
+               router.subscribe((_) => {
+                 expect(el).toHaveText('outer { hello }');
+                 expect(applicationRef.hostComponent.location.path()).toEqual('');
+                 async.done();
                });
-         }));
-    });
+             });
+       }));
 
     describe('broken app', () => {
-      beforeEachProviders(
-          () => { return [provide(ROUTER_PRIMARY_COMPONENT, {useValue: BrokenAppCmp})]; });
+      beforeEachProviders(() => [provide(ROUTER_PRIMARY_COMPONENT, {useValue: BrokenAppCmp})]);
 
       it('should rethrow exceptions from component constructors',
          inject([AsyncTestCompleter, TestComponentBuilder], (async, tcb: TestComponentBuilder) => {
@@ -91,8 +85,7 @@ export function main() {
     });
 
     describe('back button app', () => {
-      beforeEachProviders(
-          () => { return [provide(ROUTER_PRIMARY_COMPONENT, {useValue: HierarchyAppCmp})]; });
+      beforeEachProviders(() => [provide(ROUTER_PRIMARY_COMPONENT, {useValue: HierarchyAppCmp})]);
 
       it('should change the url without pushing a new history state for back navigations',
          inject([AsyncTestCompleter, TestComponentBuilder], (async, tcb: TestComponentBuilder) => {
@@ -184,7 +177,7 @@ export function main() {
                   }));
       });
     });
-    // TODO: add a test in which the child component has bindings
+
 
     describe('querystring params app', () => {
       beforeEachProviders(
@@ -243,20 +236,21 @@ export function main() {
 }
 
 
-@Component({selector: 'hello-cmp'})
-@View({template: 'hello'})
+@Component({selector: 'hello-cmp', template: 'hello'})
 class HelloCmp {
   public message: string;
 }
 
-@Component({selector: 'hello2-cmp'})
-@View({template: 'hello2'})
+@Component({selector: 'hello2-cmp', template: 'hello2'})
 class Hello2Cmp {
   public greeting: string;
 }
 
-@Component({selector: 'app-cmp'})
-@View({template: "outer {  }", directives: ROUTER_DIRECTIVES})
+@Component({
+  selector: 'app-cmp',
+  template: `outer {  }`,
+  directives: ROUTER_DIRECTIVES
+})
 @RouteConfig([new Route({path: '/', component: HelloCmp})])
 class AppCmp {
   constructor(public router: Router, public location: LocationStrategy) {}
@@ -283,20 +277,29 @@ class AppWithViewChildren implements AfterViewInit {
   afterViewInit() { this.helloCmp.message = 'Ahoy'; }
 }
 
-@Component({selector: 'parent-cmp'})
-@View({template: `parent {  }`, directives: ROUTER_DIRECTIVES})
+@Component({
+  selector: 'parent-cmp',
+  template: `parent {  }`,
+  directives: ROUTER_DIRECTIVES
+})
 @RouteConfig([new Route({path: '/child', component: HelloCmp})])
 class ParentCmp {
 }
 
-@Component({selector: 'super-parent-cmp'})
-@View({template: `super-parent {  }`, directives: ROUTER_DIRECTIVES})
+@Component({
+  selector: 'super-parent-cmp',
+  template: `super-parent {  }`,
+  directives: ROUTER_DIRECTIVES
+})
 @RouteConfig([new Route({path: '/child', component: Hello2Cmp})])
 class SuperParentCmp {
 }
 
-@Component({selector: 'app-cmp'})
-@View({template: `root {  }`, directives: ROUTER_DIRECTIVES})
+@Component({
+  selector: 'app-cmp',
+  template: `root {  }`,
+  directives: ROUTER_DIRECTIVES
+})
 @RouteConfig([
   new Route({path: '/parent/...', component: ParentCmp}),
   new Route({path: '/super-parent/...', component: SuperParentCmp})
@@ -305,28 +308,32 @@ class HierarchyAppCmp {
   constructor(public router: Router, public location: LocationStrategy) {}
 }
 
-@Component({selector: 'qs-cmp'})
-@View({template: "qParam = {{q}}"})
+@Component({selector: 'qs-cmp', template: `qParam = {{q}}`})
 class QSCmp {
   q: string;
   constructor(params: RouteParams) { this.q = params.get('q'); }
 }
 
-@Component({selector: 'app-cmp'})
-@View({template: ``, directives: ROUTER_DIRECTIVES})
+@Component({
+  selector: 'app-cmp',
+  template: ``,
+  directives: ROUTER_DIRECTIVES
+})
 @RouteConfig([new Route({path: '/qs', component: QSCmp})])
 class QueryStringAppCmp {
   constructor(public router: Router, public location: LocationStrategy) {}
 }
 
-@Component({selector: 'oops-cmp'})
-@View({template: "oh no"})
+@Component({selector: 'oops-cmp', template: "oh no"})
 class BrokenCmp {
   constructor() { throw new BaseException('oops!'); }
 }
 
-@Component({selector: 'app-cmp'})
-@View({template: `outer {  }`, directives: ROUTER_DIRECTIVES})
+@Component({
+  selector: 'app-cmp',
+  template: `outer {  }`,
+  directives: ROUTER_DIRECTIVES
+})
 @RouteConfig([new Route({path: '/cause-error', component: BrokenCmp})])
 class BrokenAppCmp {
   constructor(public router: Router, public location: LocationStrategy) {}
diff --git a/modules/angular2/test/router/integration/impl/async_route_spec_impl.ts b/modules/angular2/test/router/integration/impl/async_route_spec_impl.ts
new file mode 100644
index 0000000000..583e4ecef8
--- /dev/null
+++ b/modules/angular2/test/router/integration/impl/async_route_spec_impl.ts
@@ -0,0 +1,655 @@
+import {
+  AsyncTestCompleter,
+  beforeEach,
+  beforeEachProviders,
+  expect,
+  iit,
+  flushMicrotasks,
+  inject,
+  it,
+  TestComponentBuilder,
+  RootTestComponent,
+  xit,
+} from 'angular2/testing_internal';
+
+import {specs, compile, TEST_ROUTER_PROVIDERS, clickOnElement, getHref} from '../util';
+
+import {Router, AsyncRoute, Route, Location} from 'angular2/router';
+
+import {
+  HelloCmp,
+  helloCmpLoader,
+  UserCmp,
+  userCmpLoader,
+  TeamCmp,
+  asyncTeamLoader,
+  ParentCmp,
+  parentCmpLoader,
+  asyncParentCmpLoader,
+  asyncDefaultParentCmpLoader,
+  ParentWithDefaultCmp,
+  parentWithDefaultCmpLoader,
+  asyncRouteDataCmp
+} from './fixture_components';
+
+function getLinkElement(rtc: RootTestComponent) {
+  return rtc.debugElement.componentViewChildren[0].nativeElement;
+}
+
+function asyncRoutesWithoutChildrenWithRouteData() {
+  var fixture;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should inject route data into the component', inject([AsyncTestCompleter], (async) => {
+       compile(tcb)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute(
+                 {path: '/route-data', loader: asyncRouteDataCmp, data: {isAdmin: true}})
+           ]))
+           .then((_) => rtr.navigateByUrl('/route-data'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('true');
+             async.done();
+           });
+     }));
+
+  it('should inject empty object if the route has no data property',
+     inject([AsyncTestCompleter], (async) => {
+       compile(tcb)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/route-data-default', loader: asyncRouteDataCmp})]))
+           .then((_) => rtr.navigateByUrl('/route-data-default'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('');
+             async.done();
+           });
+     }));
+}
+
+function asyncRoutesWithoutChildrenWithoutParams() {
+  var fixture;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/test', loader: helloCmpLoader, name: 'Hello'})]))
+           .then((_) => rtr.navigateByUrl('/test'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('hello');
+             async.done();
+           });
+     }));
+
+  it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/test', loader: helloCmpLoader, name: 'Hello'})]))
+           .then((_) => rtr.navigate(['/Hello']))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('hello');
+             async.done();
+           });
+     }));
+
+  it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `go to hello | `)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/test', loader: helloCmpLoader, name: 'Hello'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(getHref(getLinkElement(fixture))).toEqual('/test');
+             async.done();
+           });
+     }));
+
+  it('should navigate from a link click',
+     inject([AsyncTestCompleter, Location], (async, location) => {
+       compile(tcb, `go to hello | `)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/test', loader: helloCmpLoader, name: 'Hello'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('go to hello | ');
+
+             rtr.subscribe((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement).toHaveText('go to hello | hello');
+               expect(location.urlChanges).toEqual(['/test']);
+               async.done();
+             });
+
+             clickOnElement(getLinkElement(fixture));
+           });
+     }));
+}
+
+
+function asyncRoutesWithoutChildrenWithParams() {
+  var fixture;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})]))
+           .then((_) => rtr.navigateByUrl('/user/igor'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
+             async.done();
+           });
+     }));
+
+  it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
+           .then((_) => rtr.navigate(['/User', {name: 'brian'}]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
+             async.done();
+           });
+     }));
+
+  it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `greet naomi | `)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(getHref(getLinkElement(fixture))).toEqual('/user/naomi');
+             async.done();
+           });
+     }));
+
+  it('should navigate from a link click',
+     inject([AsyncTestCompleter, Location], (async, location) => {
+       compile(tcb, `greet naomi | `)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('greet naomi | ');
+
+             rtr.subscribe((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement).toHaveText('greet naomi | hello naomi');
+               expect(location.urlChanges).toEqual(['/user/naomi']);
+               async.done();
+             });
+
+             clickOnElement(getLinkElement(fixture));
+           });
+     }));
+
+  it('should navigate between components with different parameters',
+     inject([AsyncTestCompleter], (async) => {
+       compile(tcb)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})]))
+           .then((_) => rtr.navigateByUrl('/user/brian'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
+           })
+           .then((_) => rtr.navigateByUrl('/user/igor'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
+             async.done();
+           });
+     }));
+}
+
+
+function asyncRoutesWithSyncChildrenWithoutDefaultRoutes() {
+  var fixture;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/a/...', loader: parentCmpLoader, name: 'Parent'})]))
+           .then((_) => rtr.navigateByUrl('/a/b'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
+             async.done();
+           });
+     }));
+
+  it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/a/...', loader: parentCmpLoader, name: 'Parent'})]))
+           .then((_) => rtr.navigate(['/Parent', 'Child']))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
+             async.done();
+           });
+     }));
+
+  it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `nav to child | outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/a/...', loader: parentCmpLoader, name: 'Parent'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(getHref(getLinkElement(fixture))).toEqual('/a');
+             async.done();
+           });
+     }));
+
+  it('should navigate from a link click',
+     inject([AsyncTestCompleter, Location], (async, location) => {
+       compile(tcb, `nav to child | outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new AsyncRoute({path: '/a/...', loader: parentCmpLoader, name: 'Parent'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('nav to child | outer {  }');
+
+             rtr.subscribe((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement)
+                   .toHaveText('nav to child | outer { inner { hello } }');
+               expect(location.urlChanges).toEqual(['/a/b']);
+               async.done();
+             });
+
+             clickOnElement(getLinkElement(fixture));
+           });
+     }));
+}
+
+
+function asyncRoutesWithSyncChildrenWithDefaultRoutes() {
+  var fixture;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute({path: '/a/...', loader: parentWithDefaultCmpLoader, name: 'Parent'})
+           ]))
+           .then((_) => rtr.navigateByUrl('/a'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
+             async.done();
+           });
+     }));
+
+  it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute({path: '/a/...', loader: parentWithDefaultCmpLoader, name: 'Parent'})
+           ]))
+           .then((_) => rtr.navigate(['/Parent']))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
+             async.done();
+           });
+     }));
+
+  it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `link to inner | outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute({path: '/a/...', loader: parentWithDefaultCmpLoader, name: 'Parent'})
+           ]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(getHref(getLinkElement(fixture))).toEqual('/a');
+             async.done();
+           });
+     }));
+
+  it('should navigate from a link click',
+     inject([AsyncTestCompleter, Location], (async, location) => {
+       compile(tcb, `link to inner | outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute({path: '/a/...', loader: parentWithDefaultCmpLoader, name: 'Parent'})
+           ]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('link to inner | outer {  }');
+
+             rtr.subscribe((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement)
+                   .toHaveText('link to inner | outer { inner { hello } }');
+               expect(location.urlChanges).toEqual(['/a/b']);
+               async.done();
+             });
+
+             clickOnElement(getLinkElement(fixture));
+           });
+     }));
+}
+
+
+function asyncRoutesWithAsyncChildrenWithoutParamsWithoutDefaultRoutes() {
+  var rootTC;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `outer {  }`)
+           .then((rtc) => {rootTC = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute({path: '/a/...', loader: asyncParentCmpLoader, name: 'Parent'})
+           ]))
+           .then((_) => rtr.navigateByUrl('/a/b'))
+           .then((_) => {
+             rootTC.detectChanges();
+             expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
+             async.done();
+           });
+     }));
+
+  it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `outer {  }`)
+           .then((rtc) => {rootTC = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute({path: '/a/...', loader: asyncParentCmpLoader, name: 'Parent'})
+           ]))
+           .then((_) => rtr.navigate(['/Parent', 'Child']))
+           .then((_) => {
+             rootTC.detectChanges();
+             expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
+             async.done();
+           });
+     }));
+
+  it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `nav to child | outer {  }`)
+           .then((rtc) => {rootTC = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute({path: '/a/...', loader: asyncParentCmpLoader, name: 'Parent'})
+           ]))
+           .then((_) => {
+             rootTC.detectChanges();
+             expect(getHref(getLinkElement(rootTC))).toEqual('/a');
+             async.done();
+           });
+     }));
+
+  it('should navigate from a link click',
+     inject([AsyncTestCompleter, Location], (async, location) => {
+       compile(tcb, `nav to child | outer {  }`)
+           .then((rtc) => {rootTC = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute({path: '/a/...', loader: asyncParentCmpLoader, name: 'Parent'})
+           ]))
+           .then((_) => {
+             rootTC.detectChanges();
+             expect(rootTC.debugElement.nativeElement).toHaveText('nav to child | outer {  }');
+
+             rtr.subscribe((_) => {
+               rootTC.detectChanges();
+               expect(rootTC.debugElement.nativeElement)
+                   .toHaveText('nav to child | outer { inner { hello } }');
+               expect(location.urlChanges).toEqual(['/a/b']);
+               async.done();
+             });
+
+             clickOnElement(getLinkElement(rootTC));
+           });
+     }));
+}
+
+
+function asyncRoutesWithAsyncChildrenWithoutParamsWithDefaultRoutes() {
+  var rootTC;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `outer {  }`)
+           .then((rtc) => {rootTC = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute(
+                 {path: '/a/...', loader: asyncDefaultParentCmpLoader, name: 'Parent'})
+           ]))
+           .then((_) => rtr.navigateByUrl('/a'))
+           .then((_) => {
+             rootTC.detectChanges();
+             expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
+             async.done();
+           });
+     }));
+
+  it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `outer {  }`)
+           .then((rtc) => {rootTC = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute(
+                 {path: '/a/...', loader: asyncDefaultParentCmpLoader, name: 'Parent'})
+           ]))
+           .then((_) => rtr.navigate(['/Parent']))
+           .then((_) => {
+             rootTC.detectChanges();
+             expect(rootTC.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
+             async.done();
+           });
+     }));
+
+  it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `nav to child | outer {  }`)
+           .then((rtc) => {rootTC = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute(
+                 {path: '/a/...', loader: asyncDefaultParentCmpLoader, name: 'Parent'})
+           ]))
+           .then((_) => {
+             rootTC.detectChanges();
+             expect(getHref(getLinkElement(rootTC))).toEqual('/a');
+             async.done();
+           });
+     }));
+
+  it('should navigate from a link click',
+     inject([AsyncTestCompleter, Location], (async, location) => {
+       compile(tcb, `nav to child | outer {  }`)
+           .then((rtc) => {rootTC = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute(
+                 {path: '/a/...', loader: asyncDefaultParentCmpLoader, name: 'Parent'})
+           ]))
+           .then((_) => {
+             rootTC.detectChanges();
+             expect(rootTC.debugElement.nativeElement).toHaveText('nav to child | outer {  }');
+
+             rtr.subscribe((_) => {
+               rootTC.detectChanges();
+               expect(rootTC.debugElement.nativeElement)
+                   .toHaveText('nav to child | outer { inner { hello } }');
+               expect(location.urlChanges).toEqual(['/a/b']);
+               async.done();
+             });
+
+             clickOnElement(getLinkElement(rootTC));
+           });
+     }));
+}
+
+
+function asyncRoutesWithAsyncChildrenWithParamsWithoutDefaultRoutes() {
+  var fixture;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `{  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute({path: '/team/:id/...', loader: asyncTeamLoader, name: 'Team'})
+           ]))
+           .then((_) => rtr.navigateByUrl('/team/angular/user/matias'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement)
+                 .toHaveText('{ team angular | user { hello matias } }');
+             async.done();
+           });
+     }));
+
+  it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `{  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute({path: '/team/:id/...', loader: asyncTeamLoader, name: 'Team'})
+           ]))
+           .then((_) => rtr.navigate(['/Team', {id: 'angular'}, 'User', {name: 'matias'}]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement)
+                 .toHaveText('{ team angular | user { hello matias } }');
+             async.done();
+           });
+     }));
+
+  it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+       compile(
+           tcb,
+           `nav to matias {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute({path: '/team/:id/...', loader: asyncTeamLoader, name: 'Team'})
+           ]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(getHref(getLinkElement(fixture))).toEqual('/team/angular');
+             async.done();
+           });
+     }));
+
+  it('should navigate from a link click',
+     inject([AsyncTestCompleter, Location], (async, location) => {
+       compile(
+           tcb,
+           `nav to matias {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config([
+             new AsyncRoute({path: '/team/:id/...', loader: asyncTeamLoader, name: 'Team'})
+           ]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('nav to matias {  }');
+
+             rtr.subscribe((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement)
+                   .toHaveText('nav to matias { team angular | user { hello matias } }');
+               expect(location.urlChanges).toEqual(['/team/angular/user/matias']);
+               async.done();
+             });
+
+             clickOnElement(getLinkElement(fixture));
+           });
+     }));
+}
+
+export function registerSpecs() {
+  specs['asyncRoutesWithoutChildrenWithRouteData'] = asyncRoutesWithoutChildrenWithRouteData;
+  specs['asyncRoutesWithoutChildrenWithoutParams'] = asyncRoutesWithoutChildrenWithoutParams;
+  specs['asyncRoutesWithoutChildrenWithParams'] = asyncRoutesWithoutChildrenWithParams;
+  specs['asyncRoutesWithSyncChildrenWithoutDefaultRoutes'] =
+      asyncRoutesWithSyncChildrenWithoutDefaultRoutes;
+  specs['asyncRoutesWithSyncChildrenWithDefaultRoutes'] =
+      asyncRoutesWithSyncChildrenWithDefaultRoutes;
+  specs['asyncRoutesWithAsyncChildrenWithoutParamsWithoutDefaultRoutes'] =
+      asyncRoutesWithAsyncChildrenWithoutParamsWithoutDefaultRoutes;
+  specs['asyncRoutesWithAsyncChildrenWithoutParamsWithDefaultRoutes'] =
+      asyncRoutesWithAsyncChildrenWithoutParamsWithDefaultRoutes;
+  specs['asyncRoutesWithAsyncChildrenWithParamsWithoutDefaultRoutes'] =
+      asyncRoutesWithAsyncChildrenWithParamsWithoutDefaultRoutes;
+}
diff --git a/modules/angular2/test/router/integration/impl/fixture_components.ts b/modules/angular2/test/router/integration/impl/fixture_components.ts
new file mode 100644
index 0000000000..075213bdf6
--- /dev/null
+++ b/modules/angular2/test/router/integration/impl/fixture_components.ts
@@ -0,0 +1,131 @@
+import {Component} from 'angular2/core';
+import {
+  AsyncRoute,
+  Route,
+  Redirect,
+  RouteConfig,
+  RouteParams,
+  RouteData,
+  ROUTER_DIRECTIVES
+} from 'angular2/router';
+import {PromiseWrapper} from 'angular2/src/facade/async';
+
+@Component({selector: 'hello-cmp', template: `{{greeting}}`})
+export class HelloCmp {
+  greeting: string;
+  constructor() { this.greeting = 'hello'; }
+}
+
+export function helloCmpLoader() {
+  return PromiseWrapper.resolve(HelloCmp);
+}
+
+
+@Component({selector: 'user-cmp', template: `hello {{user}}`})
+export class UserCmp {
+  user: string;
+  constructor(params: RouteParams) { this.user = params.get('name'); }
+}
+
+export function userCmpLoader() {
+  return PromiseWrapper.resolve(UserCmp);
+}
+
+
+@Component({
+  selector: 'parent-cmp',
+  template: `inner {  }`,
+  directives: [ROUTER_DIRECTIVES],
+})
+@RouteConfig([new Route({path: '/b', component: HelloCmp, name: 'Child'})])
+export class ParentCmp {
+}
+
+export function parentCmpLoader() {
+  return PromiseWrapper.resolve(ParentCmp);
+}
+
+
+@Component({
+  selector: 'parent-cmp',
+  template: `inner {  }`,
+  directives: [ROUTER_DIRECTIVES],
+})
+@RouteConfig([new AsyncRoute({path: '/b', loader: helloCmpLoader, name: 'Child'})])
+export class AsyncParentCmp {
+}
+
+export function asyncParentCmpLoader() {
+  return PromiseWrapper.resolve(AsyncParentCmp);
+}
+
+@Component({
+  selector: 'parent-cmp',
+  template: `inner {  }`,
+  directives: [ROUTER_DIRECTIVES],
+})
+@RouteConfig(
+    [new AsyncRoute({path: '/b', loader: helloCmpLoader, name: 'Child', useAsDefault: true})])
+export class AsyncDefaultParentCmp {
+}
+
+export function asyncDefaultParentCmpLoader() {
+  return PromiseWrapper.resolve(AsyncDefaultParentCmp);
+}
+
+
+@Component({
+  selector: 'parent-cmp',
+  template: `inner {  }`,
+  directives: [ROUTER_DIRECTIVES],
+})
+@RouteConfig([new Route({path: '/b', component: HelloCmp, name: 'Child', useAsDefault: true})])
+export class ParentWithDefaultCmp {
+}
+
+export function parentWithDefaultCmpLoader() {
+  return PromiseWrapper.resolve(ParentWithDefaultCmp);
+}
+
+
+@Component({
+  selector: 'team-cmp',
+  template: `team {{id}} | user {  }`,
+  directives: [ROUTER_DIRECTIVES],
+})
+@RouteConfig([new Route({path: '/user/:name', component: UserCmp, name: 'User'})])
+export class TeamCmp {
+  id: string;
+  constructor(params: RouteParams) { this.id = params.get('id'); }
+}
+
+@Component({
+  selector: 'team-cmp',
+  template: `team {{id}} | user {  }`,
+  directives: [ROUTER_DIRECTIVES],
+})
+@RouteConfig([new AsyncRoute({path: '/user/:name', loader: userCmpLoader, name: 'User'})])
+export class AsyncTeamCmp {
+  id: string;
+  constructor(params: RouteParams) { this.id = params.get('id'); }
+}
+
+export function asyncTeamLoader() {
+  return PromiseWrapper.resolve(AsyncTeamCmp);
+}
+
+
+@Component({selector: 'data-cmp', template: `{{myData}}`})
+export class RouteDataCmp {
+  myData: boolean;
+  constructor(data: RouteData) { this.myData = data.get('isAdmin'); }
+}
+
+export function asyncRouteDataCmp() {
+  return PromiseWrapper.resolve(RouteDataCmp);
+}
+
+@Component({selector: 'redirect-to-parent-cmp', template: 'redirect-to-parent'})
+@RouteConfig([new Redirect({path: '/child-redirect', redirectTo: ['../HelloSib']})])
+export class RedirectToParentCmp {
+}
diff --git a/modules/angular2/test/router/integration/impl/sync_route_spec_impl.ts b/modules/angular2/test/router/integration/impl/sync_route_spec_impl.ts
new file mode 100644
index 0000000000..15fbc3514c
--- /dev/null
+++ b/modules/angular2/test/router/integration/impl/sync_route_spec_impl.ts
@@ -0,0 +1,431 @@
+import {
+  AsyncTestCompleter,
+  beforeEach,
+  beforeEachProviders,
+  expect,
+  iit,
+  flushMicrotasks,
+  inject,
+  it,
+  TestComponentBuilder,
+  RootTestComponent,
+  xit,
+} from 'angular2/testing_internal';
+
+import {specs, compile, TEST_ROUTER_PROVIDERS, clickOnElement, getHref} from '../util';
+
+import {Router, Route, Location} from 'angular2/router';
+
+import {HelloCmp, UserCmp, TeamCmp, ParentCmp, ParentWithDefaultCmp} from './fixture_components';
+
+
+function getLinkElement(rtc: RootTestComponent) {
+  return rtc.debugElement.componentViewChildren[0].nativeElement;
+}
+
+function syncRoutesWithoutChildrenWithoutParams() {
+  var fixture;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb)
+           .then((rtc) => {fixture = rtc})
+           .then((_) =>
+                     rtr.config([new Route({path: '/test', component: HelloCmp, name: 'Hello'})]))
+           .then((_) => rtr.navigateByUrl('/test'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('hello');
+             async.done();
+           });
+     }));
+
+  it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb)
+           .then((rtc) => {fixture = rtc})
+           .then((_) =>
+                     rtr.config([new Route({path: '/test', component: HelloCmp, name: 'Hello'})]))
+           .then((_) => rtr.navigate(['/Hello']))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('hello');
+             async.done();
+           });
+     }));
+
+  it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `go to hello | `)
+           .then((rtc) => {fixture = rtc})
+           .then((_) =>
+                     rtr.config([new Route({path: '/test', component: HelloCmp, name: 'Hello'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(getHref(getLinkElement(fixture))).toEqual('/test');
+             async.done();
+           });
+     }));
+
+  it('should navigate from a link click',
+     inject([AsyncTestCompleter, Location], (async, location) => {
+       compile(tcb, `go to hello | `)
+           .then((rtc) => {fixture = rtc})
+           .then((_) =>
+                     rtr.config([new Route({path: '/test', component: HelloCmp, name: 'Hello'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('go to hello | ');
+
+             rtr.subscribe((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement).toHaveText('go to hello | hello');
+               expect(location.urlChanges).toEqual(['/test']);
+               async.done();
+             });
+
+             clickOnElement(getLinkElement(fixture));
+           });
+     }));
+}
+
+
+function syncRoutesWithoutChildrenWithParams() {
+  var fixture;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
+           .then((_) => rtr.navigateByUrl('/user/igor'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
+             async.done();
+           });
+     }));
+
+  it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
+           .then((_) => rtr.navigate(['/User', {name: 'brian'}]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
+             async.done();
+           });
+     }));
+
+  it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `greet naomi | `)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(getHref(getLinkElement(fixture))).toEqual('/user/naomi');
+             async.done();
+           });
+     }));
+
+  it('should navigate from a link click',
+     inject([AsyncTestCompleter, Location], (async, location) => {
+       compile(tcb, `greet naomi | `)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('greet naomi | ');
+
+             rtr.subscribe((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement).toHaveText('greet naomi | hello naomi');
+               expect(location.urlChanges).toEqual(['/user/naomi']);
+               async.done();
+             });
+
+             clickOnElement(getLinkElement(fixture));
+           });
+     }));
+
+  it('should navigate between components with different parameters',
+     inject([AsyncTestCompleter], (async) => {
+       compile(tcb)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/user/:name', component: UserCmp, name: 'User'})]))
+           .then((_) => rtr.navigateByUrl('/user/brian'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('hello brian');
+           })
+           .then((_) => rtr.navigateByUrl('/user/igor'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('hello igor');
+             async.done();
+           });
+     }));
+}
+
+
+function syncRoutesWithSyncChildrenWithoutDefaultRoutesWithoutParams() {
+  var fixture;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/a/...', component: ParentCmp, name: 'Parent'})]))
+           .then((_) => rtr.navigateByUrl('/a/b'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
+             async.done();
+           });
+     }));
+
+  it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/a/...', component: ParentCmp, name: 'Parent'})]))
+           .then((_) => rtr.navigate(['/Parent', 'Child']))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
+             async.done();
+           });
+     }));
+
+  it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `nav to child | outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/a/...', component: ParentCmp, name: 'Parent'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(getHref(getLinkElement(fixture))).toEqual('/a/b');
+             async.done();
+           });
+     }));
+
+  it('should navigate from a link click',
+     inject([AsyncTestCompleter, Location], (async, location) => {
+       compile(tcb, `nav to child | outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/a/...', component: ParentCmp, name: 'Parent'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('nav to child | outer {  }');
+
+             rtr.subscribe((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement)
+                   .toHaveText('nav to child | outer { inner { hello } }');
+               expect(location.urlChanges).toEqual(['/a/b']);
+               async.done();
+             });
+
+             clickOnElement(getLinkElement(fixture));
+           });
+     }));
+}
+
+
+function syncRoutesWithSyncChildrenWithoutDefaultRoutesWithParams() {
+  var fixture;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `{  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/team/:id/...', component: TeamCmp, name: 'Team'})]))
+           .then((_) => rtr.navigateByUrl('/team/angular/user/matias'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement)
+                 .toHaveText('{ team angular | user { hello matias } }');
+             async.done();
+           });
+     }));
+
+  it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `{  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/team/:id/...', component: TeamCmp, name: 'Team'})]))
+           .then((_) => rtr.navigate(['/Team', {id: 'angular'}, 'User', {name: 'matias'}]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement)
+                 .toHaveText('{ team angular | user { hello matias } }');
+             async.done();
+           });
+     }));
+
+  it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+       compile(
+           tcb,
+           `nav to matias {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/team/:id/...', component: TeamCmp, name: 'Team'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(getHref(getLinkElement(fixture))).toEqual('/team/angular/user/matias');
+             async.done();
+           });
+     }));
+
+  it('should navigate from a link click',
+     inject([AsyncTestCompleter, Location], (async, location) => {
+       compile(
+           tcb,
+           `nav to matias {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then((_) => rtr.config(
+                     [new Route({path: '/team/:id/...', component: TeamCmp, name: 'Team'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('nav to matias {  }');
+
+             rtr.subscribe((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement)
+                   .toHaveText('nav to matias { team angular | user { hello matias } }');
+               expect(location.urlChanges).toEqual(['/team/angular/user/matias']);
+               async.done();
+             });
+
+             clickOnElement(getLinkElement(fixture));
+           });
+     }));
+}
+
+
+function syncRoutesWithSyncChildrenWithDefaultRoutesWithoutParams() {
+  var fixture;
+  var tcb;
+  var rtr;
+
+  beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+  beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+    tcb = tcBuilder;
+    rtr = router;
+  }));
+
+  it('should navigate by URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then(
+               (_) => rtr.config(
+                   [new Route({path: '/a/...', component: ParentWithDefaultCmp, name: 'Parent'})]))
+           .then((_) => rtr.navigateByUrl('/a'))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
+             async.done();
+           });
+     }));
+
+  it('should navigate by link DSL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then(
+               (_) => rtr.config(
+                   [new Route({path: '/a/...', component: ParentWithDefaultCmp, name: 'Parent'})]))
+           .then((_) => rtr.navigate(['/Parent']))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('outer { inner { hello } }');
+             async.done();
+           });
+     }));
+
+  it('should generate a link URL', inject([AsyncTestCompleter], (async) => {
+       compile(tcb, `link to inner | outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then(
+               (_) => rtr.config(
+                   [new Route({path: '/a/...', component: ParentWithDefaultCmp, name: 'Parent'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(getHref(getLinkElement(fixture))).toEqual('/a');
+             async.done();
+           });
+     }));
+
+  it('should navigate from a link click',
+     inject([AsyncTestCompleter, Location], (async, location) => {
+       compile(tcb, `link to inner | outer {  }`)
+           .then((rtc) => {fixture = rtc})
+           .then(
+               (_) => rtr.config(
+                   [new Route({path: '/a/...', component: ParentWithDefaultCmp, name: 'Parent'})]))
+           .then((_) => {
+             fixture.detectChanges();
+             expect(fixture.debugElement.nativeElement).toHaveText('link to inner | outer {  }');
+
+             rtr.subscribe((_) => {
+               fixture.detectChanges();
+               expect(fixture.debugElement.nativeElement)
+                   .toHaveText('link to inner | outer { inner { hello } }');
+               expect(location.urlChanges).toEqual(['/a/b']);
+               async.done();
+             });
+
+             clickOnElement(getLinkElement(fixture));
+           });
+     }));
+}
+
+export function registerSpecs() {
+  specs['syncRoutesWithoutChildrenWithoutParams'] = syncRoutesWithoutChildrenWithoutParams;
+  specs['syncRoutesWithoutChildrenWithParams'] = syncRoutesWithoutChildrenWithParams;
+  specs['syncRoutesWithSyncChildrenWithoutDefaultRoutesWithoutParams'] =
+      syncRoutesWithSyncChildrenWithoutDefaultRoutesWithoutParams;
+  specs['syncRoutesWithSyncChildrenWithoutDefaultRoutesWithParams'] =
+      syncRoutesWithSyncChildrenWithoutDefaultRoutesWithParams;
+  specs['syncRoutesWithSyncChildrenWithDefaultRoutesWithoutParams'] =
+      syncRoutesWithSyncChildrenWithDefaultRoutesWithoutParams;
+}
diff --git a/modules/angular2/test/router/integration/lifecycle_hook_spec.ts b/modules/angular2/test/router/integration/lifecycle_hook_spec.ts
index 57e5d4f533..5130f2c090 100644
--- a/modules/angular2/test/router/integration/lifecycle_hook_spec.ts
+++ b/modules/angular2/test/router/integration/lifecycle_hook_spec.ts
@@ -10,7 +10,7 @@ import {
   expect,
   iit,
   inject,
-  beforeEachBindings,
+  beforeEachProviders,
   it,
   xit
 } from 'angular2/testing_internal';
@@ -25,7 +25,6 @@ import {
   ObservableWrapper
 } from 'angular2/src/facade/async';
 
-import {RootRouter} from 'angular2/src/router/router';
 import {Router, RouterOutlet, RouterLink, RouteParams} from 'angular2/router';
 import {
   RouteConfig,
@@ -35,9 +34,6 @@ import {
   Redirect
 } from 'angular2/src/router/route_config_decorator';
 
-import {SpyLocation} from 'angular2/src/mock/location_mock';
-import {Location} from 'angular2/src/router/location';
-import {RouteRegistry} from 'angular2/src/router/route_registry';
 import {
   OnActivate,
   OnDeactivate,
@@ -47,7 +43,9 @@ import {
 } from 'angular2/src/router/interfaces';
 import {CanActivate} from 'angular2/src/router/lifecycle_annotations';
 import {ComponentInstruction} from 'angular2/src/router/instruction';
-import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
+
+
+import {TEST_ROUTER_PROVIDERS, RootCmp, compile} from './util';
 
 var cmpInstanceCount;
 var log: string[];
@@ -61,17 +59,7 @@ export function main() {
     var fixture: ComponentFixture;
     var rtr;
 
-    beforeEachBindings(() => [
-      RouteRegistry,
-      DirectiveResolver,
-      provide(Location, {useClass: SpyLocation}),
-      provide(Router,
-              {
-                useFactory:
-                    (registry, location) => { return new RootRouter(registry, location, MyComp); },
-                deps: [RouteRegistry, Location]
-              })
-    ]);
+    beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
 
     beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
       tcb = tcBuilder;
@@ -81,17 +69,9 @@ export function main() {
       eventBus = new EventEmitter();
     }));
 
-    function compile(template: string = "") {
-      return tcb.overrideView(MyComp, new View({
-                                template: ('' + template + '
'),
-                                directives: [RouterOutlet, RouterLink]
-                              }))
-          .createAsync(MyComp)
-          .then((tc) => { fixture = tc; });
-    }
-
     it('should call the onActivate hook', inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => rtr.navigateByUrl('/on-activate'))
              .then((_) => {
@@ -104,7 +84,8 @@ export function main() {
 
     it('should wait for a parent component\'s onActivate hook to resolve before calling its child\'s',
        inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => {
                ObservableWrapper.subscribe(eventBus, (ev) => {
@@ -126,7 +107,8 @@ export function main() {
        }));
 
     it('should call the onDeactivate hook', inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => rtr.navigateByUrl('/on-deactivate'))
              .then((_) => rtr.navigateByUrl('/a'))
@@ -140,7 +122,8 @@ export function main() {
 
     it('should wait for a child component\'s onDeactivate hook to resolve before calling its parent\'s',
        inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => rtr.navigateByUrl('/parent-deactivate/child-deactivate'))
              .then((_) => {
@@ -165,7 +148,8 @@ export function main() {
 
     it('should reuse a component when the canReuse hook returns true',
        inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => rtr.navigateByUrl('/on-reuse/1/a'))
              .then((_) => {
@@ -187,7 +171,8 @@ export function main() {
 
     it('should not reuse a component when the canReuse hook returns false',
        inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => rtr.navigateByUrl('/never-reuse/1/a'))
              .then((_) => {
@@ -208,7 +193,8 @@ export function main() {
 
 
     it('should navigate when canActivate returns true', inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => {
                ObservableWrapper.subscribe(eventBus, (ev) => {
@@ -228,7 +214,8 @@ export function main() {
 
     it('should not navigate when canActivate returns false',
        inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => {
                ObservableWrapper.subscribe(eventBus, (ev) => {
@@ -248,7 +235,8 @@ export function main() {
 
     it('should navigate away when canDeactivate returns true',
        inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => rtr.navigateByUrl('/can-deactivate/a'))
              .then((_) => {
@@ -273,7 +261,8 @@ export function main() {
 
     it('should not navigate away when canDeactivate returns false',
        inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => rtr.navigateByUrl('/can-deactivate/a'))
              .then((_) => {
@@ -299,7 +288,8 @@ export function main() {
 
     it('should run activation and deactivation hooks in the correct order',
        inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => rtr.navigateByUrl('/activation-hooks/child'))
              .then((_) => {
@@ -325,7 +315,8 @@ export function main() {
        }));
 
     it('should only run reuse hooks when reusing', inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => rtr.navigateByUrl('/reuse-hooks/1'))
              .then((_) => {
@@ -352,7 +343,7 @@ export function main() {
        }));
 
     it('should not run reuse hooks when not reusing', inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
              .then((_) => rtr.config([new Route({path: '/...', component: LifecycleCmp})]))
              .then((_) => rtr.navigateByUrl('/reuse-hooks/1'))
              .then((_) => {
@@ -383,23 +374,16 @@ export function main() {
 }
 
 
-@Component({selector: 'a-cmp'})
-@View({template: "A"})
+@Component({selector: 'a-cmp', template: "A"})
 class A {
 }
 
 
-@Component({selector: 'b-cmp'})
-@View({template: "B"})
+@Component({selector: 'b-cmp', template: "B"})
 class B {
 }
 
 
-@Component({selector: 'my-comp'})
-class MyComp {
-  name;
-}
-
 function logHook(name: string, next: ComponentInstruction, prev: ComponentInstruction) {
   var message = name + ': ' + (isPresent(prev) ? ('/' + prev.urlPath) : 'null') + ' -> ' +
                 (isPresent(next) ? ('/' + next.urlPath) : 'null');
@@ -407,16 +391,18 @@ function logHook(name: string, next: ComponentInstruction, prev: ComponentInstru
   ObservableWrapper.callEmit(eventBus, message);
 }
 
-@Component({selector: 'activate-cmp'})
-@View({template: 'activate cmp'})
+@Component({selector: 'activate-cmp', template: 'activate cmp'})
 class ActivateCmp implements OnActivate {
   onActivate(next: ComponentInstruction, prev: ComponentInstruction) {
     logHook('activate', next, prev);
   }
 }
 
-@Component({selector: 'parent-activate-cmp'})
-@View({template: `parent {}`, directives: [RouterOutlet]})
+@Component({
+  selector: 'parent-activate-cmp',
+  template: `parent {}`,
+  directives: [RouterOutlet]
+})
 @RouteConfig([new Route({path: '/child-activate', component: ActivateCmp})])
 class ParentActivateCmp implements OnActivate {
   onActivate(next: ComponentInstruction, prev: ComponentInstruction): Promise {
@@ -426,16 +412,14 @@ class ParentActivateCmp implements OnActivate {
   }
 }
 
-@Component({selector: 'deactivate-cmp'})
-@View({template: 'deactivate cmp'})
+@Component({selector: 'deactivate-cmp', template: 'deactivate cmp'})
 class DeactivateCmp implements OnDeactivate {
   onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
     logHook('deactivate', next, prev);
   }
 }
 
-@Component({selector: 'deactivate-cmp'})
-@View({template: 'deactivate cmp'})
+@Component({selector: 'deactivate-cmp', template: 'deactivate cmp'})
 class WaitDeactivateCmp implements OnDeactivate {
   onDeactivate(next: ComponentInstruction, prev: ComponentInstruction): Promise {
     completer = PromiseWrapper.completer();
@@ -444,8 +428,11 @@ class WaitDeactivateCmp implements OnDeactivate {
   }
 }
 
-@Component({selector: 'parent-deactivate-cmp'})
-@View({template: `parent {}`, directives: [RouterOutlet]})
+@Component({
+  selector: 'parent-deactivate-cmp',
+  template: `parent {}`,
+  directives: [RouterOutlet]
+})
 @RouteConfig([new Route({path: '/child-deactivate', component: WaitDeactivateCmp})])
 class ParentDeactivateCmp implements OnDeactivate {
   onDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
@@ -453,26 +440,37 @@ class ParentDeactivateCmp implements OnDeactivate {
   }
 }
 
-@Component({selector: 'reuse-cmp'})
-@View({template: `reuse {}`, directives: [RouterOutlet]})
+@Component({
+  selector: 'reuse-cmp',
+  template: `reuse {}`,
+  directives: [RouterOutlet]
+})
 @RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
-class ReuseCmp implements OnReuse, CanReuse {
+class ReuseCmp implements OnReuse,
+    CanReuse {
   constructor() { cmpInstanceCount += 1; }
   canReuse(next: ComponentInstruction, prev: ComponentInstruction) { return true; }
   onReuse(next: ComponentInstruction, prev: ComponentInstruction) { logHook('reuse', next, prev); }
 }
 
-@Component({selector: 'never-reuse-cmp'})
-@View({template: `reuse {}`, directives: [RouterOutlet]})
+@Component({
+  selector: 'never-reuse-cmp',
+  template: `reuse {}`,
+  directives: [RouterOutlet]
+})
 @RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
-class NeverReuseCmp implements OnReuse, CanReuse {
+class NeverReuseCmp implements OnReuse,
+    CanReuse {
   constructor() { cmpInstanceCount += 1; }
   canReuse(next: ComponentInstruction, prev: ComponentInstruction) { return false; }
   onReuse(next: ComponentInstruction, prev: ComponentInstruction) { logHook('reuse', next, prev); }
 }
 
-@Component({selector: 'can-activate-cmp'})
-@View({template: `canActivate {}`, directives: [RouterOutlet]})
+@Component({
+  selector: 'can-activate-cmp',
+  template: `canActivate {}`,
+  directives: [RouterOutlet]
+})
 @RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
 @CanActivate(CanActivateCmp.canActivate)
 class CanActivateCmp {
@@ -483,8 +481,11 @@ class CanActivateCmp {
   }
 }
 
-@Component({selector: 'can-deactivate-cmp'})
-@View({template: `canDeactivate {}`, directives: [RouterOutlet]})
+@Component({
+  selector: 'can-deactivate-cmp',
+  template: `canDeactivate {}`,
+  directives: [RouterOutlet]
+})
 @RouteConfig([new Route({path: '/a', component: A}), new Route({path: '/b', component: B})])
 class CanDeactivateCmp implements CanDeactivate {
   canDeactivate(next: ComponentInstruction, prev: ComponentInstruction): Promise {
@@ -494,8 +495,7 @@ class CanDeactivateCmp implements CanDeactivate {
   }
 }
 
-@Component({selector: 'all-hooks-child-cmp'})
-@View({template: `child`})
+@Component({selector: 'all-hooks-child-cmp', template: `child`})
 @CanActivate(AllHooksChildCmp.canActivate)
 class AllHooksChildCmp implements CanDeactivate, OnDeactivate, OnActivate {
   canDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
@@ -517,11 +517,15 @@ class AllHooksChildCmp implements CanDeactivate, OnDeactivate, OnActivate {
   }
 }
 
-@Component({selector: 'all-hooks-parent-cmp'})
-@View({template: ``, directives: [RouterOutlet]})
+@Component({
+  selector: 'all-hooks-parent-cmp',
+  template: ``,
+  directives: [RouterOutlet]
+})
 @RouteConfig([new Route({path: '/child', component: AllHooksChildCmp})])
 @CanActivate(AllHooksParentCmp.canActivate)
-class AllHooksParentCmp implements CanDeactivate, OnDeactivate, OnActivate {
+class AllHooksParentCmp implements CanDeactivate,
+    OnDeactivate, OnActivate {
   canDeactivate(next: ComponentInstruction, prev: ComponentInstruction) {
     logHook('canDeactivate parent', next, prev);
     return true;
@@ -541,8 +545,7 @@ class AllHooksParentCmp implements CanDeactivate, OnDeactivate, OnActivate {
   }
 }
 
-@Component({selector: 'reuse-hooks-cmp'})
-@View({template: 'reuse hooks cmp'})
+@Component({selector: 'reuse-hooks-cmp', template: 'reuse hooks cmp'})
 @CanActivate(ReuseHooksCmp.canActivate)
 class ReuseHooksCmp implements OnActivate, OnReuse, OnDeactivate, CanReuse, CanDeactivate {
   canReuse(next: ComponentInstruction, prev: ComponentInstruction): Promise {
@@ -574,8 +577,11 @@ class ReuseHooksCmp implements OnActivate, OnReuse, OnDeactivate, CanReuse, CanD
   }
 }
 
-@Component({selector: 'lifecycle-cmp'})
-@View({template: ``, directives: [RouterOutlet]})
+@Component({
+  selector: 'lifecycle-cmp',
+  template: ``,
+  directives: [RouterOutlet]
+})
 @RouteConfig([
   new Route({path: '/a', component: A}),
   new Route({path: '/on-activate', component: ActivateCmp}),
diff --git a/modules/angular2/test/router/integration/navigation_spec.ts b/modules/angular2/test/router/integration/navigation_spec.ts
index cb420974e6..8e649ec9c2 100644
--- a/modules/angular2/test/router/integration/navigation_spec.ts
+++ b/modules/angular2/test/router/integration/navigation_spec.ts
@@ -10,7 +10,7 @@ import {
   expect,
   iit,
   inject,
-  beforeEachBindings,
+  beforeEachProviders,
   it,
   xit
 } from 'angular2/testing_internal';
@@ -18,8 +18,7 @@ import {
 import {provide, Component, View, Injector, Inject} from 'angular2/core';
 import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
 
-import {RootRouter} from 'angular2/src/router/router';
-import {Router, RouterOutlet, RouterLink, RouteParams, RouteData} from 'angular2/router';
+import {Router, RouterOutlet, RouterLink, RouteParams, RouteData, Location} from 'angular2/router';
 import {
   RouteConfig,
   Route,
@@ -28,14 +27,10 @@ import {
   Redirect
 } from 'angular2/src/router/route_config_decorator';
 
-import {SpyLocation} from 'angular2/src/mock/location_mock';
-import {Location} from 'angular2/src/router/location';
-import {RouteRegistry} from 'angular2/src/router/route_registry';
-import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
+import {TEST_ROUTER_PROVIDERS, RootCmp, compile} from './util';
 
 var cmpInstanceCount;
 var childCmpInstanceCount;
-var log: string[];
 
 export function main() {
   describe('navigation', () => {
@@ -44,37 +39,18 @@ export function main() {
     var fixture: ComponentFixture;
     var rtr;
 
-    beforeEachBindings(() => [
-      RouteRegistry,
-      DirectiveResolver,
-      provide(Location, {useClass: SpyLocation}),
-      provide(Router,
-              {
-                useFactory:
-                    (registry, location) => { return new RootRouter(registry, location, MyComp); },
-                deps: [RouteRegistry, Location]
-              })
-    ]);
+    beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
 
     beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
       tcb = tcBuilder;
       rtr = router;
       childCmpInstanceCount = 0;
       cmpInstanceCount = 0;
-      log = [];
     }));
 
-    function compile(template: string = "") {
-      return tcb.overrideView(MyComp, new View({
-                                template: ('' + template + '
'),
-                                directives: [RouterOutlet, RouterLink]
-                              }))
-          .createAsync(MyComp)
-          .then((tc) => { fixture = tc; });
-    }
-
     it('should work in a simple case', inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/test', component: HelloCmp})]))
              .then((_) => rtr.navigateByUrl('/test'))
              .then((_) => {
@@ -87,7 +63,8 @@ export function main() {
 
     it('should navigate between components with different parameters',
        inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/user/:name', component: UserCmp})]))
              .then((_) => rtr.navigateByUrl('/user/brian'))
              .then((_) => {
@@ -102,9 +79,9 @@ export function main() {
              });
        }));
 
-
     it('should navigate to child routes', inject([AsyncTestCompleter], (async) => {
-         compile('outer {  }')
+         compile(tcb, 'outer {  }')
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/a/...', component: ParentCmp})]))
              .then((_) => rtr.navigateByUrl('/a/b'))
              .then((_) => {
@@ -116,7 +93,9 @@ export function main() {
 
     it('should navigate to child routes that capture an empty path',
        inject([AsyncTestCompleter], (async) => {
-         compile('outer {  }')
+
+         compile(tcb, 'outer {  }')
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/a/...', component: ParentCmp})]))
              .then((_) => rtr.navigateByUrl('/a'))
              .then((_) => {
@@ -126,9 +105,9 @@ export function main() {
              });
        }));
 
-
     it('should navigate to child routes of async routes', inject([AsyncTestCompleter], (async) => {
-         compile('outer {  }')
+         compile(tcb, 'outer {  }')
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new AsyncRoute({path: '/a/...', loader: parentLoader})]))
              .then((_) => rtr.navigateByUrl('/a/b'))
              .then((_) => {
@@ -138,26 +117,9 @@ export function main() {
              });
        }));
 
-
-    it('should recognize and apply redirects',
-       inject([AsyncTestCompleter, Location], (async, location) => {
-         compile()
-             .then((_) => rtr.config([
-               new Redirect({path: '/original', redirectTo: '/redirected'}),
-               new Route({path: '/redirected', component: HelloCmp})
-             ]))
-             .then((_) => rtr.navigateByUrl('/original'))
-             .then((_) => {
-               fixture.detectChanges();
-               expect(fixture.debugElement.nativeElement).toHaveText('hello');
-               expect(location.urlChanges).toEqual(['/redirected']);
-               async.done();
-             });
-       }));
-
-
     it('should reuse common parent components', inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/team/:id/...', component: TeamCmp})]))
              .then((_) => rtr.navigateByUrl('/team/angular/user/rado'))
              .then((_) => {
@@ -177,7 +139,8 @@ export function main() {
 
     it('should not reuse children when parent components change',
        inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([new Route({path: '/team/:id/...', component: TeamCmp})]))
              .then((_) => rtr.navigateByUrl('/team/angular/user/rado'))
              .then((_) => {
@@ -197,7 +160,8 @@ export function main() {
        }));
 
     it('should inject route data into component', inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([
                new Route({path: '/route-data', component: RouteDataCmp, data: {isAdmin: true}})
              ]))
@@ -211,10 +175,11 @@ export function main() {
 
     it('should inject route data into component with AsyncRoute',
        inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config([
                new AsyncRoute(
-                   {path: '/route-data', loader: AsyncRouteDataCmp, data: {isAdmin: true}})
+                   {path: '/route-data', loader: asyncRouteDataCmp, data: {isAdmin: true}})
              ]))
              .then((_) => rtr.navigateByUrl('/route-data'))
              .then((_) => {
@@ -226,7 +191,8 @@ export function main() {
 
     it('should inject empty object if the route has no data property',
        inject([AsyncTestCompleter], (async) => {
-         compile()
+         compile(tcb)
+             .then((rtc) => {fixture = rtc})
              .then((_) => rtr.config(
                        [new Route({path: '/route-data-default', component: RouteDataCmp})]))
              .then((_) => rtr.navigateByUrl('/route-data-default'))
@@ -236,45 +202,28 @@ export function main() {
                async.done();
              });
        }));
-
-    describe('auxiliary routes', () => {
-      it('should recognize a simple case', inject([AsyncTestCompleter], (async) => {
-           compile()
-               .then((_) => rtr.config([new Route({path: '/...', component: AuxCmp})]))
-               .then((_) => rtr.navigateByUrl('/hello(modal)'))
-               .then((_) => {
-                 fixture.detectChanges();
-                 expect(fixture.debugElement.nativeElement)
-                     .toHaveText('main {hello} | aux {modal}');
-                 async.done();
-               });
-         }));
-    });
   });
 }
 
 
-@Component({selector: 'hello-cmp'})
-@View({template: "{{greeting}}"})
+@Component({selector: 'hello-cmp', template: `{{greeting}}`})
 class HelloCmp {
   greeting: string;
-  constructor() { this.greeting = "hello"; }
+  constructor() { this.greeting = 'hello'; }
 }
 
 
-function AsyncRouteDataCmp() {
+function asyncRouteDataCmp() {
   return PromiseWrapper.resolve(RouteDataCmp);
 }
 
-@Component({selector: 'data-cmp'})
-@View({template: "{{myData}}"})
+@Component({selector: 'data-cmp', template: `{{myData}}`})
 class RouteDataCmp {
   myData: boolean;
   constructor(data: RouteData) { this.myData = data.get('isAdmin'); }
 }
 
-@Component({selector: 'user-cmp'})
-@View({template: "hello {{user}}"})
+@Component({selector: 'user-cmp', template: `hello {{user}}`})
 class UserCmp {
   user: string;
   constructor(params: RouteParams) {
@@ -288,9 +237,9 @@ function parentLoader() {
   return PromiseWrapper.resolve(ParentCmp);
 }
 
-@Component({selector: 'parent-cmp'})
-@View({
-  template: "inner {  }",
+@Component({
+  selector: 'parent-cmp',
+  template: `inner {  }`,
   directives: [RouterOutlet],
 })
 @RouteConfig([
@@ -298,13 +247,12 @@ function parentLoader() {
   new Route({path: '/', component: HelloCmp}),
 ])
 class ParentCmp {
-  constructor() {}
 }
 
 
-@Component({selector: 'team-cmp'})
-@View({
-  template: "team {{id}} {  }",
+@Component({
+  selector: 'team-cmp',
+  template: `team {{id}} {  }`,
   directives: [RouterOutlet],
 })
 @RouteConfig([new Route({path: '/user/:name', component: UserCmp})])
@@ -315,27 +263,3 @@ class TeamCmp {
     cmpInstanceCount += 1;
   }
 }
-
-
-@Component({selector: 'my-comp'})
-class MyComp {
-  name;
-}
-
-@Component({selector: 'modal-cmp'})
-@View({template: "modal"})
-class ModalCmp {
-}
-
-@Component({selector: 'aux-cmp'})
-@View({
-  template: 'main {} | ' +
-                'aux {}',
-  directives: [RouterOutlet],
-})
-@RouteConfig([
-  new Route({path: '/hello', component: HelloCmp}),
-  new AuxRoute({path: '/modal', component: ModalCmp}),
-])
-class AuxCmp {
-}
diff --git a/modules/angular2/test/router/integration/redirect_route_spec.ts b/modules/angular2/test/router/integration/redirect_route_spec.ts
new file mode 100644
index 0000000000..7f8bb28a92
--- /dev/null
+++ b/modules/angular2/test/router/integration/redirect_route_spec.ts
@@ -0,0 +1,121 @@
+import {
+  RootTestComponent,
+  AsyncTestCompleter,
+  TestComponentBuilder,
+  beforeEach,
+  ddescribe,
+  xdescribe,
+  describe,
+  el,
+  expect,
+  iit,
+  inject,
+  beforeEachProviders,
+  it,
+  xit
+} from 'angular2/testing_internal';
+
+import {Router, RouterOutlet, RouterLink, RouteParams, RouteData, Location} from 'angular2/router';
+import {
+  RouteConfig,
+  Route,
+  AuxRoute,
+  AsyncRoute,
+  Redirect
+} from 'angular2/src/router/route_config_decorator';
+
+import {TEST_ROUTER_PROVIDERS, RootCmp, compile} from './util';
+import {HelloCmp, RedirectToParentCmp} from './impl/fixture_components';
+
+var cmpInstanceCount;
+var childCmpInstanceCount;
+
+export function main() {
+  describe('redirects', () => {
+
+    var tcb: TestComponentBuilder;
+    var rootTC: RootTestComponent;
+    var rtr;
+
+    beforeEachProviders(() => TEST_ROUTER_PROVIDERS);
+
+    beforeEach(inject([TestComponentBuilder, Router], (tcBuilder, router) => {
+      tcb = tcBuilder;
+      rtr = router;
+      childCmpInstanceCount = 0;
+      cmpInstanceCount = 0;
+    }));
+
+
+    it('should apply when navigating by URL',
+       inject([AsyncTestCompleter, Location], (async, location) => {
+         compile(tcb)
+             .then((rtc) => {rootTC = rtc})
+             .then((_) => rtr.config([
+               new Redirect({path: '/original', redirectTo: ['Hello']}),
+               new Route({path: '/redirected', component: HelloCmp, name: 'Hello'})
+             ]))
+             .then((_) => rtr.navigateByUrl('/original'))
+             .then((_) => {
+               rootTC.detectChanges();
+               expect(rootTC.debugElement.nativeElement).toHaveText('hello');
+               expect(location.urlChanges).toEqual(['/redirected']);
+               async.done();
+             });
+       }));
+
+
+    it('should recognize and apply absolute redirects',
+       inject([AsyncTestCompleter, Location], (async, location) => {
+         compile(tcb)
+             .then((rtc) => {rootTC = rtc})
+             .then((_) => rtr.config([
+               new Redirect({path: '/original', redirectTo: ['/Hello']}),
+               new Route({path: '/redirected', component: HelloCmp, name: 'Hello'})
+             ]))
+             .then((_) => rtr.navigateByUrl('/original'))
+             .then((_) => {
+               rootTC.detectChanges();
+               expect(rootTC.debugElement.nativeElement).toHaveText('hello');
+               expect(location.urlChanges).toEqual(['/redirected']);
+               async.done();
+             });
+       }));
+
+
+    it('should recognize and apply relative child redirects',
+       inject([AsyncTestCompleter, Location], (async, location) => {
+         compile(tcb)
+             .then((rtc) => {rootTC = rtc})
+             .then((_) => rtr.config([
+               new Redirect({path: '/original', redirectTo: ['./Hello']}),
+               new Route({path: '/redirected', component: HelloCmp, name: 'Hello'})
+             ]))
+             .then((_) => rtr.navigateByUrl('/original'))
+             .then((_) => {
+               rootTC.detectChanges();
+               expect(rootTC.debugElement.nativeElement).toHaveText('hello');
+               expect(location.urlChanges).toEqual(['/redirected']);
+               async.done();
+             });
+       }));
+
+
+    it('should recognize and apply relative parent redirects',
+       inject([AsyncTestCompleter, Location], (async, location) => {
+         compile(tcb)
+             .then((rtc) => {rootTC = rtc})
+             .then((_) => rtr.config([
+               new Route({path: '/original/...', component: RedirectToParentCmp}),
+               new Route({path: '/redirected', component: HelloCmp, name: 'HelloSib'})
+             ]))
+             .then((_) => rtr.navigateByUrl('/original/child-redirect'))
+             .then((_) => {
+               rootTC.detectChanges();
+               expect(rootTC.debugElement.nativeElement).toHaveText('hello');
+               expect(location.urlChanges).toEqual(['/redirected']);
+               async.done();
+             });
+       }));
+  });
+}
diff --git a/modules/angular2/test/router/integration/router_link_spec.ts b/modules/angular2/test/router/integration/router_link_spec.ts
index 5b76fd538b..6a074c06b6 100644
--- a/modules/angular2/test/router/integration/router_link_spec.ts
+++ b/modules/angular2/test/router/integration/router_link_spec.ts
@@ -9,7 +9,7 @@ import {
   expect,
   iit,
   inject,
-  beforeEachBindings,
+  beforeEachProviders,
   it,
   xit,
   TestComponentBuilder,
@@ -21,7 +21,7 @@ import {NumberWrapper} from 'angular2/src/facade/lang';
 import {PromiseWrapper} from 'angular2/src/facade/async';
 import {ListWrapper} from 'angular2/src/facade/collection';
 
-import {provide, Component, DirectiveResolver} from 'angular2/core';
+import {provide, Component, View, DirectiveResolver} from 'angular2/core';
 
 import {SpyLocation} from 'angular2/src/mock/location_mock';
 import {
@@ -35,7 +35,8 @@ import {
   Route,
   RouteParams,
   RouteConfig,
-  ROUTER_DIRECTIVES
+  ROUTER_DIRECTIVES,
+  ROUTER_PRIMARY_COMPONENT
 } from 'angular2/router';
 import {RootRouter} from 'angular2/src/router/router';
 
@@ -47,16 +48,12 @@ export function main() {
     var fixture: ComponentFixture;
     var router, location;
 
-    beforeEachBindings(() => [
+    beforeEachProviders(() => [
       RouteRegistry,
       DirectiveResolver,
       provide(Location, {useClass: SpyLocation}),
-      provide(Router,
-              {
-                useFactory:
-                    (registry, location) => { return new RootRouter(registry, location, MyComp); },
-                deps: [RouteRegistry, Location]
-              })
+      provide(ROUTER_PRIMARY_COMPONENT, {useValue: MyComp}),
+      provide(Router, {useClass: RootRouter})
     ]);
 
     beforeEach(inject([TestComponentBuilder, Router, Location], (tcBuilder, rtr, loc) => {
@@ -240,8 +237,8 @@ export function main() {
              .then((_) => router.config([new Route({path: '/...', component: AuxLinkCmp})]))
              .then((_) => router.navigateByUrl('/'))
              .then((_) => {
-               rootTC.detectChanges();
-               expect(DOM.getAttribute(rootTC.debugElement.componentViewChildren[1]
+               fixture.detectChanges();
+               expect(DOM.getAttribute(fixture.debugElement.componentViewChildren[1]
                                            .componentViewChildren[0]
                                            .nativeElement,
                                        'href'))
@@ -386,10 +383,7 @@ class MyComp {
   name;
 }
 
-@Component({
-  selector: 'user-cmp',
-  template: "hello {{user}}"
-})
+@Component({selector: 'user-cmp', template: "hello {{user}}"})
 class UserCmp {
   user: string;
   constructor(params: RouteParams) { this.user = params.get('name'); }
@@ -425,17 +419,11 @@ class NoPrefixSiblingPageCmp {
   }
 }
 
-@Component({
-  selector: 'hello-cmp',
-  template: 'hello'
-})
+@Component({selector: 'hello-cmp', template: 'hello'})
 class HelloCmp {
 }
 
-@Component({
-  selector: 'hello2-cmp',
-  template: 'hello2'
-})
+@Component({selector: 'hello2-cmp', template: 'hello2'})
 class Hello2Cmp {
 }
 
@@ -455,7 +443,6 @@ function parentCmpLoader() {
   new Route({path: '/better-grandchild', component: Hello2Cmp, name: 'BetterGrandchild'})
 ])
 class ParentCmp {
-  constructor(public router: Router) {}
 }
 
 @Component({
diff --git a/modules/angular2/test/router/integration/sync_route_spec.ts b/modules/angular2/test/router/integration/sync_route_spec.ts
new file mode 100644
index 0000000000..12a4d339fe
--- /dev/null
+++ b/modules/angular2/test/router/integration/sync_route_spec.ts
@@ -0,0 +1,24 @@
+import {
+  describeRouter,
+  ddescribeRouter,
+  describeWith,
+  describeWithout,
+  describeWithAndWithout,
+  itShouldRoute
+} from './util';
+
+import {registerSpecs} from './impl/sync_route_spec_impl';
+
+export function main() {
+  registerSpecs();
+
+  describeRouter('sync routes', () => {
+    describeWithout('children', () => { describeWithAndWithout('params', itShouldRoute); });
+
+    describeWith('sync children', () => {
+      describeWithout('default routes', () => { describeWithAndWithout('params', itShouldRoute); });
+      describeWith('default routes', () => { describeWithout('params', itShouldRoute); });
+
+    });
+  });
+}
diff --git a/modules/angular2/test/router/integration/util.ts b/modules/angular2/test/router/integration/util.ts
new file mode 100644
index 0000000000..08abbf3438
--- /dev/null
+++ b/modules/angular2/test/router/integration/util.ts
@@ -0,0 +1,133 @@
+import {provide, Provider, Component, View} from 'angular2/core';
+import {Type, isBlank} from 'angular2/src/facade/lang';
+import {BaseException} from 'angular2/src/facade/exceptions';
+
+import {
+  RootTestComponent,
+  AsyncTestCompleter,
+  TestComponentBuilder,
+  beforeEach,
+  ddescribe,
+  xdescribe,
+  describe,
+  el,
+  inject,
+  beforeEachProviders,
+  it,
+  xit
+} from 'angular2/testing_internal';
+
+import {RootRouter} from 'angular2/src/router/router';
+import {Router, ROUTER_DIRECTIVES, ROUTER_PRIMARY_COMPONENT} from 'angular2/router';
+
+import {SpyLocation} from 'angular2/src/mock/location_mock';
+import {Location} from 'angular2/src/router/location';
+import {RouteRegistry} from 'angular2/src/router/route_registry';
+import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
+import {DOM} from 'angular2/src/platform/dom/dom_adapter';
+export {ComponentFixture} from 'angular2/testing_internal';
+
+
+/**
+ * Router test helpers and fixtures
+ */
+
+@Component({
+  selector: 'root-comp',
+  template: ``,
+  directives: [ROUTER_DIRECTIVES]
+})
+export class RootCmp {
+  name: string;
+}
+
+export function compile(tcb: TestComponentBuilder,
+                        template: string = "") {
+  return tcb.overrideTemplate(RootCmp, ('' + template + '
')).createAsync(RootCmp);
+}
+
+export var TEST_ROUTER_PROVIDERS = [
+  RouteRegistry,
+  DirectiveResolver,
+  provide(Location, {useClass: SpyLocation}),
+  provide(ROUTER_PRIMARY_COMPONENT, {useValue: RootCmp}),
+  provide(Router, {useClass: RootRouter})
+];
+
+export function clickOnElement(anchorEl) {
+  var dispatchedEvent = DOM.createMouseEvent('click');
+  DOM.dispatchEvent(anchorEl, dispatchedEvent);
+  return dispatchedEvent;
+}
+
+export function getHref(elt) {
+  return DOM.getAttribute(elt, 'href');
+}
+
+
+/**
+ * Router integration suite DSL
+ */
+
+var specNameBuilder = [];
+
+// we add the specs themselves onto this map
+export var specs = {};
+
+export function describeRouter(description: string, fn: Function, exclusive = false): void {
+  var specName = descriptionToSpecName(description);
+  specNameBuilder.push(specName);
+  describe(description, fn);
+  specNameBuilder.pop();
+}
+
+export function ddescribeRouter(description: string, fn: Function, exclusive = false): void {
+  describeRouter(description, fn, true);
+}
+
+export function describeWithAndWithout(description: string, fn: Function): void {
+  // the "without" case is usually simpler, so we opt to run this spec first
+  describeWithout(description, fn);
+  describeWith(description, fn);
+}
+
+export function describeWith(description: string, fn: Function): void {
+  var specName = 'with ' + description;
+  specNameBuilder.push(specName);
+  describe(specName, fn);
+  specNameBuilder.pop();
+}
+
+export function describeWithout(description: string, fn: Function): void {
+  var specName = 'without ' + description;
+  specNameBuilder.push(specName);
+  describe(specName, fn);
+  specNameBuilder.pop();
+}
+
+function descriptionToSpecName(description: string): string {
+  return spaceCaseToCamelCase(description);
+}
+
+// this helper looks up the suite registered from the "impl" folder in this directory
+export function itShouldRoute() {
+  var specSuiteName = spaceCaseToCamelCase(specNameBuilder.join(' '));
+
+  var spec = specs[specSuiteName];
+  if (isBlank(spec)) {
+    throw new BaseException(`Router integration spec suite "${specSuiteName}" was not found.`);
+  } else {
+    // todo: remove spec from map, throw if there are extra left over??
+    spec();
+  }
+}
+
+function spaceCaseToCamelCase(str: string): string {
+  var words = str.split(' ');
+  var first = words.shift();
+  return first + words.map(title).join('');
+}
+
+function title(str: string): string {
+  return str[0].toUpperCase() + str.substring(1);
+}
diff --git a/modules/angular2/test/router/path_recognizer_spec.ts b/modules/angular2/test/router/path_recognizer_spec.ts
index 979c21a7d0..08fddc1044 100644
--- a/modules/angular2/test/router/path_recognizer_spec.ts
+++ b/modules/angular2/test/router/path_recognizer_spec.ts
@@ -12,100 +12,82 @@ import {
 
 import {PathRecognizer} from 'angular2/src/router/path_recognizer';
 import {parser, Url, RootUrl} from 'angular2/src/router/url_parser';
-import {SyncRouteHandler} from 'angular2/src/router/sync_route_handler';
-
-class DummyClass {
-  constructor() {}
-}
-
-var mockRouteHandler = new SyncRouteHandler(DummyClass);
 
 export function main() {
   describe('PathRecognizer', () => {
 
     it('should throw when given an invalid path', () => {
-      expect(() => new PathRecognizer('/hi#', mockRouteHandler))
+      expect(() => new PathRecognizer('/hi#'))
           .toThrowError(`Path "/hi#" should not include "#". Use "HashLocationStrategy" instead.`);
-      expect(() => new PathRecognizer('hi?', mockRouteHandler))
+      expect(() => new PathRecognizer('hi?'))
           .toThrowError(`Path "hi?" contains "?" which is not allowed in a route config.`);
-      expect(() => new PathRecognizer('hi;', mockRouteHandler))
+      expect(() => new PathRecognizer('hi;'))
           .toThrowError(`Path "hi;" contains ";" which is not allowed in a route config.`);
-      expect(() => new PathRecognizer('hi=', mockRouteHandler))
+      expect(() => new PathRecognizer('hi='))
           .toThrowError(`Path "hi=" contains "=" which is not allowed in a route config.`);
-      expect(() => new PathRecognizer('hi(', mockRouteHandler))
+      expect(() => new PathRecognizer('hi('))
           .toThrowError(`Path "hi(" contains "(" which is not allowed in a route config.`);
-      expect(() => new PathRecognizer('hi)', mockRouteHandler))
+      expect(() => new PathRecognizer('hi)'))
           .toThrowError(`Path "hi)" contains ")" which is not allowed in a route config.`);
-      expect(() => new PathRecognizer('hi//there', mockRouteHandler))
+      expect(() => new PathRecognizer('hi//there'))
           .toThrowError(`Path "hi//there" contains "//" which is not allowed in a route config.`);
     });
 
-    it('should return the same instruction instance when recognizing the same path', () => {
-      var rec = new PathRecognizer('/one', mockRouteHandler);
-
-      var one = new Url('one', null, null, {});
-
-      var firstMatch = rec.recognize(one);
-      var secondMatch = rec.recognize(one);
-
-      expect(firstMatch.instruction).toBe(secondMatch.instruction);
-    });
-
     describe('querystring params', () => {
       it('should parse querystring params so long as the recognizer is a root', () => {
-        var rec = new PathRecognizer('/hello/there', mockRouteHandler);
+        var rec = new PathRecognizer('/hello/there');
         var url = parser.parse('/hello/there?name=igor');
         var match = rec.recognize(url);
-        expect(match.instruction.params).toEqual({'name': 'igor'});
+        expect(match['allParams']).toEqual({'name': 'igor'});
       });
 
       it('should return a combined map of parameters with the param expected in the URL path',
          () => {
-           var rec = new PathRecognizer('/hello/:name', mockRouteHandler);
+           var rec = new PathRecognizer('/hello/:name');
            var url = parser.parse('/hello/paul?topic=success');
            var match = rec.recognize(url);
-           expect(match.instruction.params).toEqual({'name': 'paul', 'topic': 'success'});
+           expect(match['allParams']).toEqual({'name': 'paul', 'topic': 'success'});
          });
     });
 
     describe('matrix params', () => {
       it('should be parsed along with dynamic paths', () => {
-        var rec = new PathRecognizer('/hello/:id', mockRouteHandler);
+        var rec = new PathRecognizer('/hello/:id');
         var url = new Url('hello', new Url('matias', null, null, {'key': 'value'}));
         var match = rec.recognize(url);
-        expect(match.instruction.params).toEqual({'id': 'matias', 'key': 'value'});
+        expect(match['allParams']).toEqual({'id': 'matias', 'key': 'value'});
       });
 
       it('should be parsed on a static path', () => {
-        var rec = new PathRecognizer('/person', mockRouteHandler);
+        var rec = new PathRecognizer('/person');
         var url = new Url('person', null, null, {'name': 'dave'});
         var match = rec.recognize(url);
-        expect(match.instruction.params).toEqual({'name': 'dave'});
+        expect(match['allParams']).toEqual({'name': 'dave'});
       });
 
       it('should be ignored on a wildcard segment', () => {
-        var rec = new PathRecognizer('/wild/*everything', mockRouteHandler);
+        var rec = new PathRecognizer('/wild/*everything');
         var url = parser.parse('/wild/super;variable=value');
         var match = rec.recognize(url);
-        expect(match.instruction.params).toEqual({'everything': 'super;variable=value'});
+        expect(match['allParams']).toEqual({'everything': 'super;variable=value'});
       });
 
       it('should set matrix param values to true when no value is present', () => {
-        var rec = new PathRecognizer('/path', mockRouteHandler);
+        var rec = new PathRecognizer('/path');
         var url = new Url('path', null, null, {'one': true, 'two': true, 'three': '3'});
         var match = rec.recognize(url);
-        expect(match.instruction.params).toEqual({'one': true, 'two': true, 'three': '3'});
+        expect(match['allParams']).toEqual({'one': true, 'two': true, 'three': '3'});
       });
 
       it('should be parsed on the final segment of the path', () => {
-        var rec = new PathRecognizer('/one/two/three', mockRouteHandler);
+        var rec = new PathRecognizer('/one/two/three');
 
         var three = new Url('three', null, null, {'c': '3'});
         var two = new Url('two', three, null, {'b': '2'});
         var one = new Url('one', two, null, {'a': '1'});
 
         var match = rec.recognize(one);
-        expect(match.instruction.params).toEqual({'c': '3'});
+        expect(match['allParams']).toEqual({'c': '3'});
       });
     });
   });
diff --git a/modules/angular2/test/router/route_config_spec.ts b/modules/angular2/test/router/route_config_spec.ts
index ac64143273..6094178ac4 100644
--- a/modules/angular2/test/router/route_config_spec.ts
+++ b/modules/angular2/test/router/route_config_spec.ts
@@ -214,7 +214,10 @@ class HelloCmp {
 
 @Component({selector: 'app-cmp'})
 @View({template: `root {  }`, directives: ROUTER_DIRECTIVES})
-@RouteConfig([{path: '/before', redirectTo: '/after'}, {path: '/after', component: HelloCmp}])
+@RouteConfig([
+  {path: '/before', redirectTo: ['Hello']},
+  {path: '/after', component: HelloCmp, name: 'Hello'}
+])
 class RedirectAppCmp {
   constructor(public router: Router, public location: LocationStrategy) {}
 }
diff --git a/modules/angular2/test/router/route_recognizer_spec.ts b/modules/angular2/test/router/route_recognizer_spec.ts
deleted file mode 100644
index 99426fd461..0000000000
--- a/modules/angular2/test/router/route_recognizer_spec.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-import {
-  AsyncTestCompleter,
-  describe,
-  it,
-  iit,
-  ddescribe,
-  expect,
-  inject,
-  beforeEach,
-  SpyObject
-} from 'angular2/testing_internal';
-
-import {Map, StringMapWrapper} from 'angular2/src/facade/collection';
-
-import {RouteRecognizer} from 'angular2/src/router/route_recognizer';
-import {ComponentInstruction} from 'angular2/src/router/instruction';
-
-import {Route, Redirect} from 'angular2/src/router/route_config_decorator';
-import {parser} from 'angular2/src/router/url_parser';
-
-export function main() {
-  describe('RouteRecognizer', () => {
-    var recognizer;
-
-    beforeEach(() => { recognizer = new RouteRecognizer(); });
-
-
-    it('should recognize a static segment', () => {
-      recognizer.config(new Route({path: '/test', component: DummyCmpA}));
-      var solution = recognize(recognizer, '/test');
-      expect(getComponentType(solution)).toEqual(DummyCmpA);
-    });
-
-
-    it('should recognize a single slash', () => {
-      recognizer.config(new Route({path: '/', component: DummyCmpA}));
-      var solution = recognize(recognizer, '/');
-      expect(getComponentType(solution)).toEqual(DummyCmpA);
-    });
-
-
-    it('should recognize a dynamic segment', () => {
-      recognizer.config(new Route({path: '/user/:name', component: DummyCmpA}));
-      var solution = recognize(recognizer, '/user/brian');
-      expect(getComponentType(solution)).toEqual(DummyCmpA);
-      expect(solution.params).toEqual({'name': 'brian'});
-    });
-
-
-    it('should recognize a star segment', () => {
-      recognizer.config(new Route({path: '/first/*rest', component: DummyCmpA}));
-      var solution = recognize(recognizer, '/first/second/third');
-      expect(getComponentType(solution)).toEqual(DummyCmpA);
-      expect(solution.params).toEqual({'rest': 'second/third'});
-    });
-
-
-    it('should throw when given two routes that start with the same static segment', () => {
-      recognizer.config(new Route({path: '/hello', component: DummyCmpA}));
-      expect(() => recognizer.config(new Route({path: '/hello', component: DummyCmpB})))
-          .toThrowError('Configuration \'/hello\' conflicts with existing route \'/hello\'');
-    });
-
-
-    it('should throw when given two routes that have dynamic segments in the same order', () => {
-      recognizer.config(new Route({path: '/hello/:person/how/:doyoudou', component: DummyCmpA}));
-      expect(() => recognizer.config(
-                 new Route({path: '/hello/:friend/how/:areyou', component: DummyCmpA})))
-          .toThrowError(
-              'Configuration \'/hello/:friend/how/:areyou\' conflicts with existing route \'/hello/:person/how/:doyoudou\'');
-    });
-
-
-    it('should recognize redirects', () => {
-      recognizer.config(new Route({path: '/b', component: DummyCmpA}));
-      recognizer.config(new Redirect({path: '/a', redirectTo: 'b'}));
-      var solution = recognize(recognizer, '/a');
-      expect(getComponentType(solution)).toEqual(DummyCmpA);
-      expect(solution.urlPath).toEqual('b');
-    });
-
-
-    it('should not perform root URL redirect on a non-root route', () => {
-      recognizer.config(new Redirect({path: '/', redirectTo: '/foo'}));
-      recognizer.config(new Route({path: '/bar', component: DummyCmpA}));
-      var solution = recognize(recognizer, '/bar');
-      expect(solution.componentType).toEqual(DummyCmpA);
-      expect(solution.urlPath).toEqual('bar');
-    });
-
-
-    it('should perform a root URL redirect only for root routes', () => {
-      recognizer.config(new Redirect({path: '/', redirectTo: '/matias'}));
-      recognizer.config(new Route({path: '/matias', component: DummyCmpA}));
-      recognizer.config(new Route({path: '/fatias', component: DummyCmpA}));
-
-      var solution;
-
-      solution = recognize(recognizer, '/');
-      expect(solution.urlPath).toEqual('matias');
-
-      solution = recognize(recognizer, '/fatias');
-      expect(solution.urlPath).toEqual('fatias');
-
-      solution = recognize(recognizer, '');
-      expect(solution.urlPath).toEqual('matias');
-    });
-
-
-    it('should generate URLs with params', () => {
-      recognizer.config(new Route({path: '/app/user/:name', component: DummyCmpA, name: 'User'}));
-      var instruction = recognizer.generate('User', {'name': 'misko'});
-      expect(instruction.urlPath).toEqual('app/user/misko');
-    });
-
-
-    it('should generate URLs with numeric params', () => {
-      recognizer.config(new Route({path: '/app/page/:number', component: DummyCmpA, name: 'Page'}));
-      expect(recognizer.generate('Page', {'number': 42}).urlPath).toEqual('app/page/42');
-    });
-
-
-    it('should throw in the absence of required params URLs', () => {
-      recognizer.config(new Route({path: 'app/user/:name', component: DummyCmpA, name: 'User'}));
-      expect(() => recognizer.generate('User', {}))
-          .toThrowError('Route generator for \'name\' was not included in parameters passed.');
-    });
-
-
-    it('should throw if the route alias is not CamelCase', () => {
-      expect(() => recognizer.config(
-                 new Route({path: 'app/user/:name', component: DummyCmpA, name: 'user'})))
-          .toThrowError(
-              `Route "app/user/:name" with name "user" does not begin with an uppercase letter. Route names should be CamelCase like "User".`);
-    });
-
-
-    describe('params', () => {
-      it('should recognize parameters within the URL path', () => {
-        recognizer.config(new Route({path: 'profile/:name', component: DummyCmpA, name: 'User'}));
-        var solution = recognize(recognizer, '/profile/matsko?comments=all');
-        expect(solution.params).toEqual({'name': 'matsko', 'comments': 'all'});
-      });
-
-
-      it('should generate and populate the given static-based route with querystring params',
-         () => {
-           recognizer.config(
-               new Route({path: 'forum/featured', component: DummyCmpA, name: 'ForumPage'}));
-
-           var params = {'start': 10, 'end': 100};
-
-           var result = recognizer.generate('ForumPage', params);
-           expect(result.urlPath).toEqual('forum/featured');
-           expect(result.urlParams).toEqual(['start=10', 'end=100']);
-         });
-
-
-      it('should prefer positional params over query params', () => {
-        recognizer.config(new Route({path: 'profile/:name', component: DummyCmpA, name: 'User'}));
-
-        var solution = recognize(recognizer, '/profile/yegor?name=igor');
-        expect(solution.params).toEqual({'name': 'yegor'});
-      });
-
-
-      it('should ignore matrix params for the top-level component', () => {
-        recognizer.config(new Route({path: '/home/:subject', component: DummyCmpA, name: 'User'}));
-        var solution = recognize(recognizer, '/home;sort=asc/zero;one=1?two=2');
-        expect(solution.params).toEqual({'subject': 'zero', 'two': '2'});
-      });
-    });
-  });
-}
-
-function recognize(recognizer: RouteRecognizer, url: string): ComponentInstruction {
-  return recognizer.recognize(parser.parse(url))[0].instruction;
-}
-
-function getComponentType(routeMatch: ComponentInstruction): any {
-  return routeMatch.componentType;
-}
-
-class DummyCmpA {}
-class DummyCmpB {}
diff --git a/modules/angular2/test/router/route_registry_spec.ts b/modules/angular2/test/router/route_registry_spec.ts
index 3b906bb749..423a88d264 100644
--- a/modules/angular2/test/router/route_registry_spec.ts
+++ b/modules/angular2/test/router/route_registry_spec.ts
@@ -11,7 +11,7 @@ import {
 } from 'angular2/testing_internal';
 
 import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
-import {Type} from 'angular2/src/facade/lang';
+import {Type, IS_DART} from 'angular2/src/facade/lang';
 
 import {RouteRegistry} from 'angular2/src/router/route_registry';
 import {
@@ -21,20 +21,19 @@ import {
   AuxRoute,
   AsyncRoute
 } from 'angular2/src/router/route_config_decorator';
-import {stringifyInstruction} from 'angular2/src/router/instruction';
-import {IS_DART} from 'angular2/src/facade/lang';
+
 
 export function main() {
   describe('RouteRegistry', () => {
     var registry;
 
-    beforeEach(() => { registry = new RouteRegistry(); });
+    beforeEach(() => { registry = new RouteRegistry(RootHostCmp); });
 
     it('should match the full URL', inject([AsyncTestCompleter], (async) => {
          registry.config(RootHostCmp, new Route({path: '/', component: DummyCmpA}));
          registry.config(RootHostCmp, new Route({path: '/test', component: DummyCmpB}));
 
-         registry.recognize('/test', RootHostCmp)
+         registry.recognize('/test', [])
              .then((instruction) => {
                expect(instruction.component.componentType).toBe(DummyCmpB);
                async.done();
@@ -45,28 +44,35 @@ export function main() {
       registry.config(RootHostCmp,
                       new Route({path: '/first/...', component: DummyParentCmp, name: 'FirstCmp'}));
 
-      expect(stringifyInstruction(registry.generate(['FirstCmp', 'SecondCmp'], RootHostCmp)))
+      var instr = registry.generate(['FirstCmp', 'SecondCmp'], []);
+      expect(stringifyInstruction(instr)).toEqual('first/second');
+
+      expect(stringifyInstruction(registry.generate(['SecondCmp'], [instr])))
           .toEqual('first/second');
-      expect(stringifyInstruction(registry.generate(['SecondCmp'], DummyParentCmp)))
-          .toEqual('second');
-    });
-
-    xit('should generate URLs that account for redirects', () => {
-      registry.config(
-          RootHostCmp,
-          new Route({path: '/first/...', component: DummyParentRedirectCmp, name: 'FirstCmp'}));
-
-      expect(stringifyInstruction(registry.generate(['FirstCmp'], RootHostCmp)))
+      expect(stringifyInstruction(registry.generate(['./SecondCmp'], [instr])))
           .toEqual('first/second');
     });
 
-    xit('should generate URLs in a hierarchy of redirects', () => {
+    it('should generate URLs that account for default routes', () => {
       registry.config(
           RootHostCmp,
-          new Route({path: '/first/...', component: DummyMultipleRedirectCmp, name: 'FirstCmp'}));
+          new Route({path: '/first/...', component: ParentWithDefaultRouteCmp, name: 'FirstCmp'}));
 
-      expect(stringifyInstruction(registry.generate(['FirstCmp'], RootHostCmp)))
-          .toEqual('first/second/third');
+      var instruction = registry.generate(['FirstCmp'], []);
+
+      expect(instruction.toLinkUrl()).toEqual('first');
+      expect(instruction.toRootUrl()).toEqual('first/second');
+    });
+
+    it('should generate URLs in a hierarchy of default routes', () => {
+      registry.config(
+          RootHostCmp,
+          new Route({path: '/first/...', component: MultipleDefaultCmp, name: 'FirstCmp'}));
+
+      var instruction = registry.generate(['FirstCmp'], []);
+
+      expect(instruction.toLinkUrl()).toEqual('first');
+      expect(instruction.toRootUrl()).toEqual('first/second/third');
     });
 
     it('should generate URLs with params', () => {
@@ -74,14 +80,14 @@ export function main() {
           RootHostCmp,
           new Route({path: '/first/:param/...', component: DummyParentParamCmp, name: 'FirstCmp'}));
 
-      var url = stringifyInstruction(registry.generate(
-          ['FirstCmp', {param: 'one'}, 'SecondCmp', {param: 'two'}], RootHostCmp));
+      var url = stringifyInstruction(
+          registry.generate(['FirstCmp', {param: 'one'}, 'SecondCmp', {param: 'two'}], []));
       expect(url).toEqual('first/one/second/two');
     });
 
     it('should generate params as an empty StringMap when no params are given', () => {
       registry.config(RootHostCmp, new Route({path: '/test', component: DummyCmpA, name: 'Test'}));
-      var instruction = registry.generate(['Test'], RootHostCmp);
+      var instruction = registry.generate(['Test'], []);
       expect(instruction.component.params).toEqual({});
     });
 
@@ -91,20 +97,20 @@ export function main() {
              RootHostCmp,
              new AsyncRoute({path: '/first/...', loader: asyncParentLoader, name: 'FirstCmp'}));
 
-         expect(() => registry.generate(['FirstCmp', 'SecondCmp'], RootHostCmp))
-             .toThrowError('Could not find route named "SecondCmp".');
+         var instruction = registry.generate(['FirstCmp', 'SecondCmp'], []);
 
-         registry.recognize('/first/second', RootHostCmp)
+         expect(stringifyInstruction(instruction)).toEqual('first');
+
+         registry.recognize('/first/second', [])
              .then((_) => {
-               expect(
-                   stringifyInstruction(registry.generate(['FirstCmp', 'SecondCmp'], RootHostCmp)))
-                   .toEqual('first/second');
+               var instruction = registry.generate(['FirstCmp', 'SecondCmp'], []);
+               expect(stringifyInstruction(instruction)).toEqual('first/second');
                async.done();
              });
        }));
 
     it('should throw when generating a url and a parent has no config', () => {
-      expect(() => registry.generate(['FirstCmp', 'SecondCmp'], RootHostCmp))
+      expect(() => registry.generate(['FirstCmp', 'SecondCmp'], []))
           .toThrowError('Component "RootHostCmp" has no route config.');
     });
 
@@ -113,7 +119,7 @@ export function main() {
                       new Route({path: '/primary', component: DummyCmpA, name: 'Primary'}));
       registry.config(RootHostCmp, new AuxRoute({path: '/aux', component: DummyCmpB, name: 'Aux'}));
 
-      expect(stringifyInstruction(registry.generate(['Primary', ['Aux']], RootHostCmp)))
+      expect(stringifyInstruction(registry.generate(['Primary', ['Aux']], [])))
           .toEqual('primary(aux)');
     });
 
@@ -121,7 +127,7 @@ export function main() {
          registry.config(RootHostCmp, new Route({path: '/:site', component: DummyCmpB}));
          registry.config(RootHostCmp, new Route({path: '/home', component: DummyCmpA}));
 
-         registry.recognize('/home', RootHostCmp)
+         registry.recognize('/home', [])
              .then((instruction) => {
                expect(instruction.component.componentType).toBe(DummyCmpA);
                async.done();
@@ -132,7 +138,7 @@ export function main() {
          registry.config(RootHostCmp, new Route({path: '/:site', component: DummyCmpA}));
          registry.config(RootHostCmp, new Route({path: '/*site', component: DummyCmpB}));
 
-         registry.recognize('/home', RootHostCmp)
+         registry.recognize('/home', [])
              .then((instruction) => {
                expect(instruction.component.componentType).toBe(DummyCmpA);
                async.done();
@@ -143,7 +149,7 @@ export function main() {
          registry.config(RootHostCmp, new Route({path: '/:first/*rest', component: DummyCmpA}));
          registry.config(RootHostCmp, new Route({path: '/*all', component: DummyCmpB}));
 
-         registry.recognize('/some/path', RootHostCmp)
+         registry.recognize('/some/path', [])
              .then((instruction) => {
                expect(instruction.component.componentType).toBe(DummyCmpA);
                async.done();
@@ -154,7 +160,7 @@ export function main() {
          registry.config(RootHostCmp, new Route({path: '/first/:second', component: DummyCmpA}));
          registry.config(RootHostCmp, new Route({path: '/:first/:second', component: DummyCmpB}));
 
-         registry.recognize('/first/second', RootHostCmp)
+         registry.recognize('/first/second', [])
              .then((instruction) => {
                expect(instruction.component.componentType).toBe(DummyCmpA);
                async.done();
@@ -168,7 +174,7 @@ export function main() {
          registry.config(RootHostCmp,
                          new Route({path: '/first/:second/third', component: DummyCmpA}));
 
-         registry.recognize('/first/second/third', RootHostCmp)
+         registry.recognize('/first/second/third', [])
              .then((instruction) => {
                expect(instruction.component.componentType).toBe(DummyCmpB);
                async.done();
@@ -178,7 +184,7 @@ export function main() {
     it('should match the full URL using child components', inject([AsyncTestCompleter], (async) => {
          registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyParentCmp}));
 
-         registry.recognize('/first/second', RootHostCmp)
+         registry.recognize('/first/second', [])
              .then((instruction) => {
                expect(instruction.component.componentType).toBe(DummyParentCmp);
                expect(instruction.child.component.componentType).toBe(DummyCmpB);
@@ -190,11 +196,14 @@ export function main() {
        inject([AsyncTestCompleter], (async) => {
          registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyAsyncCmp}));
 
-         registry.recognize('/first/second', RootHostCmp)
+         registry.recognize('/first/second', [])
              .then((instruction) => {
                expect(instruction.component.componentType).toBe(DummyAsyncCmp);
-               expect(instruction.child.component.componentType).toBe(DummyCmpB);
-               async.done();
+
+               instruction.child.resolveComponent().then((childComponentInstruction) => {
+                 expect(childComponentInstruction.componentType).toBe(DummyCmpB);
+                 async.done();
+               });
              });
        }));
 
@@ -203,11 +212,14 @@ export function main() {
          registry.config(RootHostCmp,
                          new AsyncRoute({path: '/first/...', loader: asyncParentLoader}));
 
-         registry.recognize('/first/second', RootHostCmp)
+         registry.recognize('/first/second', [])
              .then((instruction) => {
                expect(instruction.component.componentType).toBe(DummyParentCmp);
-               expect(instruction.child.component.componentType).toBe(DummyCmpB);
-               async.done();
+
+               instruction.child.resolveComponent().then((childType) => {
+                 expect(childType.componentType).toBe(DummyCmpB);
+                 async.done();
+               });
              });
        }));
 
@@ -242,15 +254,15 @@ export function main() {
     it('should throw when linkParams are not terminal', () => {
       registry.config(RootHostCmp,
                       new Route({path: '/first/...', component: DummyParentCmp, name: 'First'}));
-      expect(() => { registry.generate(['First'], RootHostCmp); })
-          .toThrowError('Link "["First"]" does not resolve to a terminal or async instruction.');
+      expect(() => { registry.generate(['First'], []); })
+          .toThrowError('Link "["First"]" does not resolve to a terminal instruction.');
     });
 
     it('should match matrix params on child components and query params on the root component',
        inject([AsyncTestCompleter], (async) => {
          registry.config(RootHostCmp, new Route({path: '/first/...', component: DummyParentCmp}));
 
-         registry.recognize('/first/second;filter=odd?comments=all', RootHostCmp)
+         registry.recognize('/first/second;filter=odd?comments=all', [])
              .then((instruction) => {
                expect(instruction.component.componentType).toBe(DummyParentCmp);
                expect(instruction.component.params).toEqual({'comments': 'all'});
@@ -276,13 +288,18 @@ export function main() {
               sort: 'asc',
             }
           ],
-          RootHostCmp));
+          []));
       expect(url).toEqual('first/one/second/two;sort=asc?query=cats');
     });
 
   });
 }
 
+function stringifyInstruction(instruction): string {
+  return instruction.toRootUrl();
+}
+
+
 function asyncParentLoader() {
   return PromiseWrapper.resolve(DummyParentCmp);
 }
@@ -300,26 +317,22 @@ class DummyAsyncCmp {
 class DummyCmpA {}
 class DummyCmpB {}
 
-@RouteConfig([
-  new Redirect({path: '/', redirectTo: '/third'}),
-  new Route({path: '/third', component: DummyCmpB, name: 'ThirdCmp'})
-])
-class DummyRedirectCmp {
+@RouteConfig(
+    [new Route({path: '/third', component: DummyCmpB, name: 'ThirdCmp', useAsDefault: true})])
+class DefaultRouteCmp {
 }
 
 
 @RouteConfig([
-  new Redirect({path: '/', redirectTo: '/second'}),
-  new Route({path: '/second/...', component: DummyRedirectCmp, name: 'SecondCmp'})
+  new Route(
+      {path: '/second/...', component: DefaultRouteCmp, name: 'SecondCmp', useAsDefault: true})
 ])
-class DummyMultipleRedirectCmp {
+class MultipleDefaultCmp {
 }
 
-@RouteConfig([
-  new Redirect({path: '/', redirectTo: '/second'}),
-  new Route({path: '/second', component: DummyCmpB, name: 'SecondCmp'})
-])
-class DummyParentRedirectCmp {
+@RouteConfig(
+    [new Route({path: '/second', component: DummyCmpB, name: 'SecondCmp', useAsDefault: true})])
+class ParentWithDefaultRouteCmp {
 }
 
 @RouteConfig([new Route({path: '/second', component: DummyCmpB, name: 'SecondCmp'})])
diff --git a/modules/angular2/test/router/router_link_spec.ts b/modules/angular2/test/router/router_link_spec.ts
index 49c90696fc..46af69f37a 100644
--- a/modules/angular2/test/router/router_link_spec.ts
+++ b/modules/angular2/test/router/router_link_spec.ts
@@ -8,7 +8,7 @@ import {
   expect,
   iit,
   inject,
-  beforeEachBindings,
+  beforeEachProviders,
   it,
   xit,
   TestComponentBuilder
@@ -27,24 +27,20 @@ import {
   RouterOutlet,
   Route,
   RouteParams,
-  Instruction,
   ComponentInstruction
 } from 'angular2/router';
 
 import {DOM} from 'angular2/src/platform/dom/dom_adapter';
-import {ComponentInstruction_} from 'angular2/src/router/instruction';
-import {PathRecognizer} from 'angular2/src/router/path_recognizer';
-import {SyncRouteHandler} from 'angular2/src/router/sync_route_handler';
+import {ResolvedInstruction} from 'angular2/src/router/instruction';
 
-let dummyPathRecognizer = new PathRecognizer('', new SyncRouteHandler(null));
 let dummyInstruction =
-    new Instruction(new ComponentInstruction_('detail', [], dummyPathRecognizer), null, {});
+    new ResolvedInstruction(new ComponentInstruction('detail', [], null, null, true, 0), null, {});
 
 export function main() {
   describe('router-link directive', function() {
     var tcb: TestComponentBuilder;
 
-    beforeEachBindings(() => [
+    beforeEachProviders(() => [
       provide(Location, {useValue: makeDummyLocation()}),
       provide(Router, {useValue: makeDummyRouter()})
     ]);
@@ -106,11 +102,6 @@ export function main() {
   });
 }
 
-@Component({selector: 'my-comp'})
-class MyComp {
-  name;
-}
-
 @Component({selector: 'user-cmp'})
 @View({template: "hello {{user}}"})
 class UserCmp {
diff --git a/modules/angular2/test/router/router_spec.ts b/modules/angular2/test/router/router_spec.ts
index 1fb2a3fb11..af3190e623 100644
--- a/modules/angular2/test/router/router_spec.ts
+++ b/modules/angular2/test/router/router_spec.ts
@@ -8,7 +8,7 @@ import {
   expect,
   inject,
   beforeEach,
-  beforeEachBindings
+  beforeEachProviders
 } from 'angular2/testing_internal';
 import {SpyRouterOutlet} from './spies';
 import {Type} from 'angular2/src/facade/lang';
@@ -18,9 +18,8 @@ import {ListWrapper} from 'angular2/src/facade/collection';
 import {Router, RootRouter} from 'angular2/src/router/router';
 import {SpyLocation} from 'angular2/src/mock/location_mock';
 import {Location} from 'angular2/src/router/location';
-import {stringifyInstruction} from 'angular2/src/router/instruction';
 
-import {RouteRegistry} from 'angular2/src/router/route_registry';
+import {RouteRegistry, ROUTER_PRIMARY_COMPONENT} from 'angular2/src/router/route_registry';
 import {RouteConfig, AsyncRoute, Route} from 'angular2/src/router/route_config_decorator';
 import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
 
@@ -30,16 +29,12 @@ export function main() {
   describe('Router', () => {
     var router, location;
 
-    beforeEachBindings(() => [
+    beforeEachProviders(() => [
       RouteRegistry,
       DirectiveResolver,
       provide(Location, {useClass: SpyLocation}),
-      provide(Router,
-              {
-                useFactory:
-                    (registry, location) => { return new RootRouter(registry, location, AppCmp); },
-                deps: [RouteRegistry, Location]
-              })
+      provide(ROUTER_PRIMARY_COMPONENT, {useValue: AppCmp}),
+      provide(Router, {useClass: RootRouter})
     ]);
 
 
@@ -225,6 +220,11 @@ export function main() {
   });
 }
 
+
+function stringifyInstruction(instruction): string {
+  return instruction.toRootUrl();
+}
+
 function loader(): Promise {
   return PromiseWrapper.resolve(DummyComponent);
 }
diff --git a/tools/broccoli/trees/node_tree.ts b/tools/broccoli/trees/node_tree.ts
index da6e6647eb..29d911ddf2 100644
--- a/tools/broccoli/trees/node_tree.ts
+++ b/tools/broccoli/trees/node_tree.ts
@@ -30,7 +30,7 @@ module.exports = function makeNodeTree(projects, destinationPath) {
 
       // we call browser's bootstrap
       'angular2/test/router/route_config_spec.ts',
-      'angular2/test/router/integration/router_integration_spec.ts',
+      'angular2/test/router/integration/bootstrap_spec.ts',
 
       // we check the public api by importing angular2/angular2
       'angular2/test/symbol_inspector/**/*.ts',