feat(WebWorker): Add Router Support for WebWorker Apps

Closes #3563.
This commit is contained in:
Jason Teplitz
2016-01-26 21:07:12 -08:00
committed by Rado Kirov
parent 800c8f196f
commit 8bea667a0b
43 changed files with 839 additions and 153 deletions
@@ -0,0 +1,58 @@
import {Injectable} from 'angular2/core';
import {History, Location} from 'angular2/src/facade/browser';
import {UrlChangeListener} from './platform_location';
import {PlatformLocation} from './platform_location';
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
/**
* `PlatformLocation` encapsulates all of the direct calls to platform APIs.
* This class should not be used directly by an application developer. Instead, use
* {@link Location}.
*/
@Injectable()
export class BrowserPlatformLocation extends PlatformLocation {
private _location: Location;
private _history: History;
constructor() {
super();
this._init();
}
// This is moved to its own method so that `MockPlatformLocationStrategy` can overwrite it
/** @internal */
_init() {
this._location = DOM.getLocation();
this._history = DOM.getHistory();
}
/** @internal */
get location(): Location { return this._location; }
getBaseHrefFromDOM(): string { return DOM.getBaseHref(); }
onPopState(fn: UrlChangeListener): void {
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
}
onHashChange(fn: UrlChangeListener): void {
DOM.getGlobalEventTarget('window').addEventListener('hashchange', fn, false);
}
get pathname(): string { return this._location.pathname; }
get search(): string { return this._location.search; }
get hash(): string { return this._location.hash; }
set pathname(newPath: string) { this._location.pathname = newPath; }
pushState(state: any, title: string, url: string): void {
this._history.pushState(state, title, url);
}
replaceState(state: any, title: string, url: string): void {
this._history.replaceState(state, title, url);
}
forward(): void { this._history.forward(); }
back(): void { this._history.back(); }
}
@@ -5,7 +5,7 @@ import {
APP_BASE_HREF,
normalizeQueryParams
} from './location_strategy';
import {EventListener} from 'angular2/src/facade/browser';
import {UrlChangeListener} from './platform_location';
import {isPresent} from 'angular2/src/facade/lang';
import {PlatformLocation} from './platform_location';
@@ -58,7 +58,7 @@ export class HashLocationStrategy extends LocationStrategy {
}
}
onPopState(fn: EventListener): void {
onPopState(fn: UrlChangeListener): void {
this._platformLocation.onPopState(fn);
this._platformLocation.onHashChange(fn);
}
@@ -1,5 +1,6 @@
import {CONST_EXPR} from 'angular2/src/facade/lang';
import {OpaqueToken} from 'angular2/core';
import {UrlChangeListener} from './platform_location';
/**
* `LocationStrategy` is responsible for representing and reading route state
@@ -24,7 +25,7 @@ export abstract class LocationStrategy {
abstract replaceState(state: any, title: string, url: string, queryParams: string): void;
abstract forward(): void;
abstract back(): void;
abstract onPopState(fn: (_: any) => any): void;
abstract onPopState(fn: UrlChangeListener): void;
abstract getBaseHref(): string;
}
@@ -1,5 +1,4 @@
import {Injectable, Inject, Optional} from 'angular2/core';
import {EventListener, History, Location} from 'angular2/src/facade/browser';
import {isBlank} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {
@@ -8,7 +7,7 @@ import {
normalizeQueryParams,
joinWithSlash
} from './location_strategy';
import {PlatformLocation} from './platform_location';
import {PlatformLocation, UrlChangeListener} from './platform_location';
/**
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
@@ -75,7 +74,7 @@ export class PathLocationStrategy extends LocationStrategy {
this._baseHref = href;
}
onPopState(fn: EventListener): void {
onPopState(fn: UrlChangeListener): void {
this._platformLocation.onPopState(fn);
this._platformLocation.onHashChange(fn);
}
@@ -1,50 +1,48 @@
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
import {Injectable} from 'angular2/core';
import {EventListener, History, Location} from 'angular2/src/facade/browser';
/**
* `PlatformLocation` encapsulates all of the direct calls to platform APIs.
* This class should not be used directly by an application developer. Instead, use
* {@link Location}.
*
* `PlatformLocation` encapsulates all calls to DOM apis, which allows the Router to be platform
* agnostic.
* This means that we can have different implementation of `PlatformLocation` for the different
* platforms
* that angular supports. For example, the default `PlatformLocation` is {@link
* BrowserPlatformLocation},
* however when you run your app in a WebWorker you use {@link WebWorkerPlatformLocation}.
*
* The `PlatformLocation` class is used directly by all implementations of {@link LocationStrategy}
* when
* they need to interact with the DOM apis like pushState, popState, etc...
*
* {@link LocationStrategy} in turn is used by the {@link Location} service which is used directly
* by
* the {@link Router} in order to navigate between routes. Since all interactions between {@link
* Router} /
* {@link Location} / {@link LocationStrategy} and DOM apis flow through the `PlatformLocation`
* class
* they are all platform independent.
*/
@Injectable()
export class PlatformLocation {
private _location: Location;
private _history: History;
export abstract class PlatformLocation {
abstract getBaseHrefFromDOM(): string;
abstract onPopState(fn: UrlChangeListener): void;
abstract onHashChange(fn: UrlChangeListener): void;
constructor() { this._init(); }
pathname: string;
search: string;
hash: string;
// This is moved to its own method so that `MockPlatformLocationStrategy` can overwrite it
/** @internal */
_init() {
this._location = DOM.getLocation();
this._history = DOM.getHistory();
}
abstract replaceState(state: any, title: string, url: string): void;
getBaseHrefFromDOM(): string { return DOM.getBaseHref(); }
abstract pushState(state: any, title: string, url: string): void;
onPopState(fn: EventListener): void {
DOM.getGlobalEventTarget('window').addEventListener('popstate', fn, false);
}
abstract forward(): void;
onHashChange(fn: EventListener): void {
DOM.getGlobalEventTarget('window').addEventListener('hashchange', fn, false);
}
get pathname(): string { return this._location.pathname; }
get search(): string { return this._location.search; }
get hash(): string { return this._location.hash; }
set pathname(newPath: string) { this._location.pathname = newPath; }
pushState(state: any, title: string, url: string): void {
this._history.pushState(state, title, url);
}
replaceState(state: any, title: string, url: string): void {
this._history.replaceState(state, title, url);
}
forward(): void { this._history.forward(); }
back(): void { this._history.back(); }
abstract back(): void;
}
/**
* A serializable version of the event from onPopState or onHashChange
*/
export interface UrlChangeEvent { type: string; }
export interface UrlChangeListener { (e: UrlChangeEvent): any; }
@@ -0,0 +1,42 @@
// import {ROUTER_PROVIDERS_COMMON} from './router_providers_common';
import {ROUTER_PROVIDERS_COMMON} from 'angular2/router';
import {Provider} from 'angular2/core';
import {CONST_EXPR} from 'angular2/src/facade/lang';
import {BrowserPlatformLocation} from './browser_platform_location';
import {PlatformLocation} from './platform_location';
/**
* A list of {@link Provider}s. To use the router, you must add this to your application.
*
* ### Example ([live demo](http://plnkr.co/edit/iRUP8B5OUbxCWQ3AcIDm))
*
* ```
* import {Component} from 'angular2/core';
* import {
* ROUTER_DIRECTIVES,
* ROUTER_PROVIDERS,
* RouteConfig
* } from 'angular2/router';
*
* @Component({directives: [ROUTER_DIRECTIVES]})
* @RouteConfig([
* {...},
* ])
* class AppCmp {
* // ...
* }
*
* bootstrap(AppCmp, [ROUTER_PROVIDERS]);
* ```
*/
export const ROUTER_PROVIDERS: any[] = CONST_EXPR([
ROUTER_PROVIDERS_COMMON,
CONST_EXPR(new Provider(PlatformLocation, {useClass: BrowserPlatformLocation})),
]);
/**
* Use {@link ROUTER_PROVIDERS} instead.
*
* @deprecated
*/
export const ROUTER_BINDINGS = ROUTER_PROVIDERS;
@@ -0,0 +1,40 @@
import {LocationStrategy} from 'angular2/src/router/location_strategy';
import {PathLocationStrategy} from 'angular2/src/router/path_location_strategy';
import {Router, RootRouter} from 'angular2/src/router/router';
import {RouteRegistry, ROUTER_PRIMARY_COMPONENT} from 'angular2/src/router/route_registry';
import {Location} from 'angular2/src/router/location';
import {CONST_EXPR, Type} from 'angular2/src/facade/lang';
import {ApplicationRef, OpaqueToken, Provider} from 'angular2/core';
import {BaseException} from 'angular2/src/facade/exceptions';
/**
* The Platform agnostic ROUTER PROVIDERS
*/
export const ROUTER_PROVIDERS_COMMON: any[] = CONST_EXPR([
RouteRegistry,
CONST_EXPR(new Provider(LocationStrategy, {useClass: PathLocationStrategy})),
Location,
CONST_EXPR(new Provider(
Router,
{
useFactory: routerFactory,
deps: CONST_EXPR([RouteRegistry, Location, ROUTER_PRIMARY_COMPONENT, ApplicationRef])
})),
CONST_EXPR(new Provider(
ROUTER_PRIMARY_COMPONENT,
{useFactory: routerPrimaryComponentFactory, deps: CONST_EXPR([ApplicationRef])}))
]);
function routerFactory(registry: RouteRegistry, location: Location, primaryComponent: Type,
appRef: ApplicationRef): RootRouter {
var rootRouter = new RootRouter(registry, location, primaryComponent);
appRef.registerDisposeListener(() => rootRouter.dispose());
return rootRouter;
}
function routerPrimaryComponentFactory(app: ApplicationRef): Type {
if (app.componentTypes.length == 0) {
throw new BaseException("Bootstrap at least one component before injecting Router.");
}
return app.componentTypes[0];
}