diff --git a/modules/angular2/src/router/router.ts b/modules/angular2/src/router/router.ts index d98fd3fdbd..c80f4717bc 100644 --- a/modules/angular2/src/router/router.ts +++ b/modules/angular2/src/router/router.ts @@ -260,6 +260,7 @@ export class Router { } private _emitNavigationFinish(url): void { ObservableWrapper.callEmit(this._subject, url); } + _emitNavigationFail(url): void { ObservableWrapper.callError(this._subject, url); } private _afterPromiseFinishNavigating(promise: Promise): Promise { return PromiseWrapper.catchError(promise.then((_) => this._finishNavigating()), (err) => { @@ -367,8 +368,8 @@ export class Router { /** * Subscribe to URL updates from the router */ - subscribe(onNext: (value: any) => void): Object { - return ObservableWrapper.subscribe(this._subject, onNext); + subscribe(onNext: (value: any) => void, onError?: (value: any) => void): Object { + return ObservableWrapper.subscribe(this._subject, onNext, onError); } @@ -451,31 +452,35 @@ export class RootRouter extends Router { // we call recognize ourselves this.recognize(change['url']) .then((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; - } - - // Because we've opted to use All hashchange events occur outside Angular. - // However, apps that are migrating might have hash links that operate outside - // angular to which routing must respond. - // 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); + 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; } - } else { - this._location.go(emitPath, emitQuery); - } - }); + var emitPath = instruction.toUrlPath(); + var emitQuery = instruction.toUrlQuery(); + if (emitPath.length > 0 && emitPath[0] != '/') { + emitPath = '/' + emitPath; + } + + // Because we've opted to use All hashchange events occur outside Angular. + // However, apps that are migrating might have hash links that operate outside + // angular to which routing must respond. + // 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']); + } }); }); diff --git a/modules/angular2/test/router/router_spec.ts b/modules/angular2/test/router/router_spec.ts index 27ce712d27..687ca936d1 100644 --- a/modules/angular2/test/router/router_spec.ts +++ b/modules/angular2/test/router/router_spec.ts @@ -145,6 +145,22 @@ export function main() { }); })); + + it('should trigger the onError callback of a router change subscription if the URL does not match a route', + inject([AsyncTestCompleter], (async) => { + var outlet = makeDummyOutlet(); + + router.registerPrimaryOutlet(outlet) + .then((_) => router.config([new Route({path: '/a', component: DummyComponent})])) + .then((_) => { + router.subscribe((_) => {}, (url) => { + expect(url).toEqual('b'); + async.done(); + }); + (location).simulateHashChange('b'); + }); + })); + it('should navigate after being configured', inject([AsyncTestCompleter], (async) => { var outlet = makeDummyOutlet();