text: var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
      var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
      if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
      else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
      return c > 3 && r && Object.defineProperty(target, key, r), r;
  };
  var __metadata = (this && this.__metadata) || function (k, v) {
      if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
  };
  var __param = (this && this.__param) || function (paramIndex, decorator) {
      return function (target, key) { decorator(target, key, paramIndex); }
  };
  import { PromiseWrapper, EventEmitter, ObservableWrapper } from '../src/facade/async';
  import { Map, StringMapWrapper } from '../src/facade/collection';
  import { isBlank, isPresent, Type } from '../src/facade/lang';
  import { BaseException } from '../src/facade/exceptions';
  import { Location } from '@angular/common';
  import { RouteRegistry, ROUTER_PRIMARY_COMPONENT } from './route_registry';
  import { getCanActivateHook } from './lifecycle/route_lifecycle_reflector';
  import { Injectable, Inject } from '@angular/core';
  let _resolveToTrue = PromiseWrapper.resolve(true);
  let _resolveToFalse = PromiseWrapper.resolve(false);
  /**
   * The `Router` is responsible for mapping URLs to components.
   *
   * You can see the state of the router by inspecting the read-only field `router.navigating`.
   * This may be useful for showing a spinner, for instance.
   *
   * ## Concepts
   *
   * Routers and component instances have a 1:1 correspondence.
   *
   * The router holds reference to a number of {@link RouterOutlet}.
   * An outlet is a placeholder that the router dynamically fills in depending on the current URL.
   *
   * When the router navigates from a URL, it must first recognize it and serialize it into an
   * `Instruction`.
   * The router uses the `RouteRegistry` to get an `Instruction`.
   */
  export let Router = class Router {
      constructor(registry, parent, hostComponent, root) {
          this.registry = registry;
          this.parent = parent;
          this.hostComponent = hostComponent;
          this.root = root;
          this.navigating = false;
          /**
           * The current `Instruction` for the router
           */
          this.currentInstruction = null;
          this._currentNavigation = _resolveToTrue;
          this._outlet = null;
          this._auxRouters = new Map();
          this._subject = new EventEmitter();
      }
      /**
       * Constructs a child router. You probably don't need to use this unless you're writing a reusable
       * component.
       */
      childRouter(hostComponent) {
          return this._childRouter = new ChildRouter(this, hostComponent);
      }
      /**
       * Constructs a child router. You probably don't need to use this unless you're writing a reusable
       * component.
       */
      auxRouter(hostComponent) { return new ChildRouter(this, hostComponent); }
      /**
       * Register an outlet to be notified of primary route changes.
       *
       * You probably don't need to use this unless you're writing a reusable component.
       */
      registerPrimaryOutlet(outlet) {
          if (isPresent(outlet.name)) {
              throw new BaseException(`registerPrimaryOutlet expects to be called with an unnamed outlet.`);
          }
          if (isPresent(this._outlet)) {
              throw new BaseException(`Primary outlet is already registered.`);
          }
          this._outlet = outlet;
          if (isPresent(this.currentInstruction)) {
              return this.commit(this.currentInstruction, false);
          }
          return _resolveToTrue;
      }
      /**
       * Unregister an outlet (because it was destroyed, etc).
       *
       * You probably don't need to use this unless you're writing a custom outlet implementation.
       */
      unregisterPrimaryOutlet(outlet) {
          if (isPresent(outlet.name)) {
              throw new BaseException(`registerPrimaryOutlet expects to be called with an unnamed outlet.`);
          }
          this._outlet = null;
      }
      /**
       * Register an outlet to notified of auxiliary route changes.
       *
       * You probably don't need to use this unless you're writing a reusable component.
       */
      registerAuxOutlet(outlet) {
          var outletName = outlet.name;
          if (isBlank(outletName)) {
              throw new BaseException(`registerAuxOutlet expects to be called with an outlet with a name.`);
          }
          var router = this.auxRouter(this.hostComponent);
          this._auxRouters.set(outletName, router);
          router._outlet = outlet;
          var auxInstruction;
          if (isPresent(this.currentInstruction) &&
              isPresent(auxInstruction = this.currentInstruction.auxInstruction[outletName])) {
              return router.commit(auxInstruction);
          }
          return _resolveToTrue;
      }
      /**
       * Given an instruction, returns `true` if the instruction is currently active,
       * otherwise `false`.
       */
      isRouteActive(instruction) {
          var router = this;
          if (isBlank(this.currentInstruction)) {
              return false;
          }
          // `instruction` corresponds to the root router
          while (isPresent(router.parent) && isPresent(instruction.child)) {
              router = router.parent;
              instruction = instruction.child;
          }
          if (isBlank(instruction.component) || isBlank(this.currentInstruction.component) ||
              this.currentInstruction.component.routeName != instruction.component.routeName) {
              return false;
          }
          let paramEquals = true;
          if (isPresent(this.currentInstruction.component.params)) {
              StringMapWrapper.forEach(instruction.component.params, (value, key) => {
                  if (this.currentInstruction.component.params[key] !== value) {
                      paramEquals = false;
                  }
              });
          }
          return paramEquals;
      }
      /**
       * Dynamically update the routing configuration and trigger a navigation.
       *
       * ### Usage
       *
       * ```
       * router.config([
       *   { 'path': '/', 'component': IndexComp },
       *   { 'path': '/user/:id', 'component': UserComp },
       * ]);
       * ```
       */
      config(definitions) {
          definitions.forEach((routeDefinition) => { this.registry.config(this.hostComponent, routeDefinition); });
          return this.renavigate();
      }
      /**
       * Navigate based on the provided Route Link DSL. It's preferred to navigate with this method
       * over `navigateByUrl`.
       *
       * ### Usage
       *
       * This method takes an array representing the Route Link DSL:
       * ```
       * ['./MyCmp', {param: 3}]
       * ```
       * See the {@link RouterLink} directive for more.
       */
      navigate(linkParams) {
          var instruction = this.generate(linkParams);
          return this.navigateByInstruction(instruction, false);
      }
      /**
       * Navigate to a URL. Returns a promise that resolves when navigation is complete.
       * It's preferred to navigate with `navigate` instead of this method, since URLs are more brittle.
       *
       * If the given URL begins with a `/`, router will navigate absolutely.
       * If the given URL does not begin with `/`, the router will navigate relative to this component.
       */
      navigateByUrl(url, _skipLocationChange = false) {
          return this._currentNavigation = this._currentNavigation.then((_) => {
              this.lastNavigationAttempt = url;
              this._startNavigating();
              return this._afterPromiseFinishNavigating(this.recognize(url).then((instruction) => {
                  if (isBlank(instruction)) {
                      return false;
                  }
                  return this._navigate(instruction, _skipLocationChange);
              }));
          });
      }
      /**
       * Navigate via the provided instruction. Returns a promise that resolves when navigation is
       * complete.
       */
      navigateByInstruction(instruction, _skipLocationChange = false) {
          if (isBlank(instruction)) {
              return _resolveToFalse;
          }
          return this._currentNavigation = this._currentNavigation.then((_) => {
              this._startNavigating();
              return this._afterPromiseFinishNavigating(this._navigate(instruction, _skipLocationChange));
          });
      }
      /** @internal */
      _settleInstruction(instruction) {
          return instruction.resolveComponent().then((_) => {
              var unsettledInstructions = [];
              if (isPresent(instruction.component)) {
                  instruction.component.reuse = false;
              }
              if (isPresent(instruction.child)) {
                  unsettledInstructions.push(this._settleInstruction(instruction.child));
              }
              StringMapWrapper.forEach(instruction.auxInstruction, (instruction, _) => {
                  unsettledInstructions.push(this._settleInstruction(instruction));
              });
              return PromiseWrapper.all(unsettledInstructions);
          });
      }
      /** @internal */
      _navigate(instruction, _skipLocationChange) {
          return this._settleInstruction(instruction)
              .then((_) => this._routerCanReuse(instruction))
              .then((_) => this._canActivate(instruction))
              .then((result) => {
              if (!result) {
                  return false;
              }
              return this._routerCanDeactivate(instruction)
                  .then((result) => {
                  if (result) {
                      return this.commit(instruction, _skipLocationChange)
                          .then((_) => {
                          this._emitNavigationFinish(instruction.toRootUrl());
                          return true;
                      });
                  }
              });
          });
      }
      _emitNavigationFinish(url) { ObservableWrapper.callEmit(this._subject, url); }
      /** @internal */
      _emitNavigationFail(url) { ObservableWrapper.callError(this._subject, url); }
      _afterPromiseFinishNavigating(promise) {
          return PromiseWrapper.catchError(promise.then((_) => this._finishNavigating()), (err) => {
              this._finishNavigating();
              throw err;
          });
      }
      /*
       * Recursively set reuse flags
       */
      /** @internal */
      _routerCanReuse(instruction) {
          if (isBlank(this._outlet)) {
              return _resolveToFalse;
          }
          if (isBlank(instruction.component)) {
              return _resolveToTrue;
          }
          return this._outlet.routerCanReuse(instruction.component)
              .then((result) => {
              instruction.component.reuse = result;
              if (result && isPresent(this._childRouter) && isPresent(instruction.child)) {
                  return this._childRouter._routerCanReuse(instruction.child);
              }
          });
      }
      _canActivate(nextInstruction) {
          return canActivateOne(nextInstruction, this.currentInstruction);
      }
      _routerCanDeactivate(instruction) {
          if (isBlank(this._outlet)) {
              return _resolveToTrue;
          }
          var next;
          var childInstruction = null;
          var reuse = false;
          var componentInstruction = null;
          if (isPresent(instruction)) {
              childInstruction = instruction.child;
              componentInstruction = instruction.component;
              reuse = isBlank(instruction.component) || instruction.component.reuse;
          }
          if (reuse) {
              next = _resolveToTrue;
          }
          else {
              next = this._outlet.routerCanDeactivate(componentInstruction);
          }
          // TODO: aux route lifecycle hooks
          return next.then((result) => {
              if (result == false) {
                  return false;
              }
              if (isPresent(this._childRouter)) {
                  // TODO: ideally, this closure would map to async-await in Dart.
                  // For now, casting to any to suppress an error.
                  return this._childRouter._routerCanDeactivate(childInstruction);
              }
              return true;
          });
      }
      /**
       * Updates this router and all descendant routers according to the given instruction
       */
      commit(instruction, _skipLocationChange = false) {
          this.currentInstruction = instruction;
          var next = _resolveToTrue;
          if (isPresent(this._outlet) && isPresent(instruction.component)) {
              var componentInstruction = instruction.component;
              if (componentInstruction.reuse) {
                  next = this._outlet.reuse(componentInstruction);
              }
              else {
                  next =
                      this.deactivate(instruction).then((_) => this._outlet.activate(componentInstruction));
              }
              if (isPresent(instruction.child)) {
                  next = next.then((_) => {
                      if (isPresent(this._childRouter)) {
                          return this._childRouter.commit(instruction.child);
                      }
                  });
              }
          }
          var promises = [];
          this._auxRouters.forEach((router, name) => {
              if (isPresent(instruction.auxInstruction[name])) {
                  promises.push(router.commit(instruction.auxInstruction[name]));
              }
          });
          return next.then((_) => PromiseWrapper.all(promises));
      }
      /** @internal */
      _startNavigating() { this.navigating = true; }
      /** @internal */
      _finishNavigating() { this.navigating = false; }
      /**
       * Subscribe to URL updates from the router
       */
      subscribe(onNext, onError) {
          return ObservableWrapper.subscribe(this._subject, onNext, onError);
      }
      /**
       * Removes the contents of this router's outlet and all descendant outlets
       */
      deactivate(instruction) {
          var childInstruction = null;
          var componentInstruction = null;
          if (isPresent(instruction)) {
              childInstruction = instruction.child;
              componentInstruction = instruction.component;
          }
          var next = _resolveToTrue;
          if (isPresent(this._childRouter)) {
              next = this._childRouter.deactivate(childInstruction);
          }
          if (isPresent(this._outlet)) {
              next = next.then((_) => this._outlet.deactivate(componentInstruction));
          }
          // TODO: handle aux routes
          return next;
      }
      /**
       * Given a URL, returns an instruction representing the component graph
       */
      recognize(url) {
          var ancestorComponents = this._getAncestorInstructions();
          return this.registry.recognize(url, ancestorComponents);
      }
      _getAncestorInstructions() {
          var ancestorInstructions = [this.currentInstruction];
          var ancestorRouter = this;
          while (isPresent(ancestorRouter = ancestorRouter.parent)) {
              ancestorInstructions.unshift(ancestorRouter.currentInstruction);
          }
          return ancestorInstructions;
      }
      /**
       * Navigates to either the last URL successfully navigated to, or the last URL requested if the
       * router has yet to successfully navigate.
       */
      renavigate() {
          if (isBlank(this.lastNavigationAttempt)) {
              return this._currentNavigation;
          }
          return this.navigateByUrl(this.lastNavigationAttempt);
      }
      /**
       * Generate an `Instruction` based on the provided Route Link DSL.
       */
      generate(linkParams) {
          var ancestorInstructions = this._getAncestorInstructions();
          return this.registry.generate(linkParams, ancestorInstructions);
      }
  };
  Router = __decorate([
      Injectable(), 
      __metadata('design:paramtypes', [RouteRegistry, Router, Object, Router])
  ], Router);
  export let RootRouter = class RootRouter extends Router {
      constructor(registry, location, primaryComponent) {
          super(registry, null, primaryComponent);
          this.root = this;
          this._location = location;
          this._locationSub = this._location.subscribe((change) => {
              // we call recognize ourselves
              this.recognize(change['url'])
                  .then((instruction) => {
                  if (isPresent(instruction)) {
                      this.navigateByInstruction(instruction, isPresent(change['pop']))
                          .then((_) => {
                          // this is a popstate event; no need to change the URL
                          if (isPresent(change['pop']) && change['type'] != 'hashchange') {
                              return;
                          }
                          var emitPath = instruction.toUrlPath();
                          var emitQuery = instruction.toUrlQuery();
                          if (emitPath.length > 0 && emitPath[0] != '/') {
                              emitPath = '/' + emitPath;
                          }
                          // We've opted to use pushstate and popState APIs regardless of whether you
                          // an app uses HashLocationStrategy or PathLocationStrategy.
                          // However, apps that are migrating might have hash links that operate outside
                          // angular to which routing must respond.
                          // Therefore we know that all hashchange events occur outside Angular.
                          // To support these cases where we respond to hashchanges and redirect as a
                          // result, we need to replace the top item on the stack.
                          if (change['type'] == 'hashchange') {
                              if (instruction.toRootUrl() != this._location.path()) {
                                  this._location.replaceState(emitPath, emitQuery);
                              }
                          }
                          else {
                              this._location.go(emitPath, emitQuery);
                          }
                      });
                  }
                  else {
                      this._emitNavigationFail(change['url']);
                  }
              });
          });
          this.registry.configFromComponent(primaryComponent);
          this.navigateByUrl(location.path());
      }
      commit(instruction, _skipLocationChange = false) {
          var emitPath = instruction.toUrlPath();
          var emitQuery = instruction.toUrlQuery();
          if (emitPath.length > 0 && emitPath[0] != '/') {
              emitPath = '/' + emitPath;
          }
          var promise = super.commit(instruction);
          if (!_skipLocationChange) {
              promise = promise.then((_) => { this._location.go(emitPath, emitQuery); });
          }
          return promise;
      }
      dispose() {
          if (isPresent(this._locationSub)) {
              ObservableWrapper.dispose(this._locationSub);
              this._locationSub = null;
          }
      }
  };
  RootRouter = __decorate([
      Injectable(),
      __param(2, Inject(ROUTER_PRIMARY_COMPONENT)), 
      __metadata('design:paramtypes', [RouteRegistry, Location, Type])
  ], RootRouter);
  class ChildRouter extends Router {
      constructor(parent, hostComponent) {
          super(parent.registry, parent, hostComponent, parent.root);
          this.parent = parent;
      }
      navigateByUrl(url, _skipLocationChange = false) {
          // Delegate navigation to the root router
          return this.parent.navigateByUrl(url, _skipLocationChange);
      }
      navigateByInstruction(instruction, _skipLocationChange = false) {
          // Delegate navigation to the root router
          return this.parent.navigateByInstruction(instruction, _skipLocationChange);
      }
  }
  function canActivateOne(nextInstruction, prevInstruction) {
      var next = _resolveToTrue;
      if (isBlank(nextInstruction.component)) {
          return next;
      }
      if (isPresent(nextInstruction.child)) {
          next = canActivateOne(nextInstruction.child, isPresent(prevInstruction) ? prevInstruction.child : null);
      }
      return next.then((result) => {
          if (result == false) {
              return false;
          }
          if (nextInstruction.component.reuse) {
              return true;
          }
          var hook = getCanActivateHook(nextInstruction.component.componentType);
          if (isPresent(hook)) {
              return hook(nextInstruction.component, isPresent(prevInstruction) ? prevInstruction.component : null);
          }
          return true;
      });
  }
  //# sourceMappingURL=router.js.map