Permalink
Browse files

feat(router): add support for ng1/ng2 migration (#12160)

  • Loading branch information...
vsavkin authored and alxhub committed Oct 20, 2016
1 parent b0a03fc commit 8b9ab44eee720810311eec60a1c58e1a78c23ab9
@@ -21,7 +21,6 @@ function createNode(curr: TreeNode<ActivatedRouteSnapshot>, prevState?: TreeNode
if (prevState && equalRouteSnapshots(prevState.value.snapshot, curr.value)) {
const value = prevState.value;
value._futureSnapshot = curr.value;
-
const children = createOrReuseChildren(curr, prevState);
return new TreeNode<ActivatedRoute>(value, children);
@@ -18,6 +18,7 @@ export {RouterOutletMap} from './router_outlet_map';
export {NoPreloading, PreloadAllModules, PreloadingStrategy} from './router_preloader';
export {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './router_state';
export {PRIMARY_OUTLET, Params} from './shared';
-export {DefaultUrlSerializer, UrlSegment, UrlSerializer, UrlTree} from './url_tree';
+export {UrlHandlingStrategy} from './url_handling_strategy';
+export {DefaultUrlSerializer, UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
export * from './private_export'
@@ -30,6 +30,7 @@ import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
import {RouterOutletMap} from './router_outlet_map';
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
import {NavigationCancelingError, PRIMARY_OUTLET, Params} from './shared';
+import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
import {andObservables, forEach, merge, shallowEqual, waitForMap, wrapIntoObservable} from './utils/collection';
import {TreeNode} from './utils/tree';
@@ -285,6 +286,9 @@ function defaultErrorHandler(error: any): any {
*/
export class Router {
private currentUrlTree: UrlTree;
+ private rawUrlTree: UrlTree;
+ private lastNavigation: UrlTree;
+
private currentRouterState: RouterState;
private locationSubscription: Subscription;
private routerEvents: Subject<Event>;
@@ -303,6 +307,11 @@ export class Router {
*/
navigated: boolean = false;
+ /**
+ * Extracts and merges URLs. Used for Angular 1 to Angular 2 migrations.
+ */
+ urlHandlingStrategy: UrlHandlingStrategy = new DefaultUrlHandlingStrategy();
+
/**
* Creates the router service.
*/
@@ -314,6 +323,7 @@ export class Router {
this.resetConfig(config);
this.routerEvents = new Subject<Event>();
this.currentUrlTree = createEmptyUrlTree();
+ this.rawUrlTree = this.currentUrlTree;
this.configLoader = new RouterConfigLoader(loader, compiler);
this.currentRouterState = createEmptyState(this.currentUrlTree, this.rootComponentType);
}
@@ -344,12 +354,20 @@ export class Router {
// Zone.current.wrap is needed because of the issue with RxJS scheduler,
// which does not work properly with zone.js in IE and Safari
this.locationSubscription = <any>this.location.subscribe(Zone.current.wrap((change: any) => {
- const tree = this.urlSerializer.parse(change['url']);
- // we fire multiple events for a single URL change
- // we should navigate only once
- return this.currentUrlTree.toString() !== tree.toString() ?
- this.scheduleNavigation(tree, {skipLocationChange: change['pop'], replaceUrl: true}) :
- null;
+ const rawUrlTree = this.urlSerializer.parse(change['url']);
+ const tree = this.urlHandlingStrategy.extract(rawUrlTree);
+
+
+ setTimeout(() => {
+ // we fire multiple events for a single URL change
+ // we should navigate only once
+ if (!this.lastNavigation || this.lastNavigation.toString() !== tree.toString()) {
+ this.scheduleNavigation(
+ rawUrlTree, tree, {skipLocationChange: change['pop'], replaceUrl: true});
+ } else {
+ this.rawUrlTree = rawUrlTree;
+ }
+ }, 0);
}));
}
@@ -470,10 +488,10 @@ export class Router {
navigateByUrl(url: string|UrlTree, extras: NavigationExtras = {skipLocationChange: false}):
Promise<boolean> {
if (url instanceof UrlTree) {
- return this.scheduleNavigation(url, extras);
+ return this.scheduleNavigation(this.rawUrlTree, url, extras);
} else {
const urlTree = this.urlSerializer.parse(url);
- return this.scheduleNavigation(urlTree, extras);
+ return this.scheduleNavigation(this.rawUrlTree, urlTree, extras);
}
}
@@ -500,7 +518,7 @@ export class Router {
*/
navigate(commands: any[], extras: NavigationExtras = {skipLocationChange: false}):
Promise<boolean> {
- return this.scheduleNavigation(this.createUrlTree(commands, extras), extras);
+ return this.scheduleNavigation(this.rawUrlTree, this.createUrlTree(commands, extras), extras);
}
/**
@@ -525,16 +543,34 @@ export class Router {
}
}
- private scheduleNavigation(url: UrlTree, extras: NavigationExtras): Promise<boolean> {
- const id = ++this.navigationId;
- this.routerEvents.next(new NavigationStart(id, this.serializeUrl(url)));
- return Promise.resolve().then(
- (_) => this.runNavigate(url, extras.skipLocationChange, extras.replaceUrl, id));
+ private scheduleNavigation(rawUrl: UrlTree, url: UrlTree, extras: NavigationExtras):
+ Promise<boolean> {
+ if (this.urlHandlingStrategy.shouldProcessUrl(url)) {
+ const id = ++this.navigationId;
+ this.routerEvents.next(new NavigationStart(id, this.serializeUrl(url)));
+
+ return Promise.resolve().then(
+ (_) => this.runNavigate(
+ rawUrl, url, extras.skipLocationChange, extras.replaceUrl, id, null));
+
+ // we cannot process the current URL, but we could process the previous one =>
+ // we need to do some cleanup
+ } else if (this.urlHandlingStrategy.shouldProcessUrl(this.rawUrlTree)) {
+ const id = ++this.navigationId;
+ this.routerEvents.next(new NavigationStart(id, this.serializeUrl(url)));
+
+ return Promise.resolve().then(
+ (_) => this.runNavigate(
+ rawUrl, url, false, false, id, createEmptyState(url, this.rootComponentType)));
+ } else {
+ this.rawUrlTree = rawUrl;
+ return Promise.resolve(null);
+ }
}
private runNavigate(
- url: UrlTree, shouldPreventPushState: boolean, shouldReplaceUrl: boolean,
- id: number): Promise<boolean> {
+ rawUrl: UrlTree, url: UrlTree, shouldPreventPushState: boolean, shouldReplaceUrl: boolean,
+ id: number, precreatedState: RouterState): Promise<boolean> {
if (id !== this.navigationId) {
this.location.go(this.urlSerializer.serialize(this.currentUrlTree));
this.routerEvents.next(new NavigationCancel(
@@ -553,23 +589,33 @@ export class Router {
const storedState = this.currentRouterState;
const storedUrl = this.currentUrlTree;
- const redirectsApplied$ = applyRedirects(this.injector, this.configLoader, url, this.config);
-
- const snapshot$ = mergeMap.call(redirectsApplied$, (u: UrlTree) => {
- appliedUrl = u;
- return recognize(
- this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl));
- });
-
- const emitRecognzied$ = map.call(snapshot$, (newRouterStateSnapshot: RouterStateSnapshot) => {
- this.routerEvents.next(new RoutesRecognized(
- id, this.serializeUrl(url), this.serializeUrl(appliedUrl), newRouterStateSnapshot));
- return newRouterStateSnapshot;
- });
-
- const routerState$ = map.call(emitRecognzied$, (routerStateSnapshot: RouterStateSnapshot) => {
- return createRouterState(routerStateSnapshot, this.currentRouterState);
- });
+ let routerState$: any;
+
+ if (!precreatedState) {
+ const redirectsApplied$ =
+ applyRedirects(this.injector, this.configLoader, url, this.config);
+
+ const snapshot$ = mergeMap.call(redirectsApplied$, (u: UrlTree) => {
+ appliedUrl = u;
+ return recognize(
+ this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl));
+ });
+
+ const emitRecognzied$ =
+ map.call(snapshot$, (newRouterStateSnapshot: RouterStateSnapshot) => {
+ this.routerEvents.next(new RoutesRecognized(
+ id, this.serializeUrl(url), this.serializeUrl(appliedUrl),
+ newRouterStateSnapshot));
+ return newRouterStateSnapshot;
+ });
+
+ routerState$ = map.call(emitRecognzied$, (routerStateSnapshot: RouterStateSnapshot) => {
+ return createRouterState(routerStateSnapshot, this.currentRouterState);
+ });
+ } else {
+ appliedUrl = url;
+ routerState$ = of (precreatedState);
+ }
const preactivation$ = map.call(routerState$, (newState: RouterState) => {
state = newState;
@@ -595,11 +641,14 @@ export class Router {
return;
}
+ this.lastNavigation = appliedUrl;
this.currentUrlTree = appliedUrl;
+ this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, rawUrl);
+
this.currentRouterState = state;
if (!shouldPreventPushState) {
- let path = this.urlSerializer.serialize(appliedUrl);
+ let path = this.urlSerializer.serialize(this.rawUrlTree);
if (this.location.isCurrentPathEqualTo(path) || shouldReplaceUrl) {
this.location.replaceState(path);
} else {
@@ -641,7 +690,8 @@ export class Router {
if (id === this.navigationId) {
this.currentRouterState = storedState;
this.currentUrlTree = storedUrl;
- this.location.replaceState(this.serializeUrl(storedUrl));
+ this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, rawUrl);
+ this.location.replaceState(this.serializeUrl(this.rawUrlTree));
}
});
});
@@ -18,6 +18,7 @@ import {ROUTES} from './router_config_loader';
import {RouterOutletMap} from './router_outlet_map';
import {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
import {ActivatedRoute} from './router_state';
+import {UrlHandlingStrategy} from './url_handling_strategy';
import {DefaultUrlSerializer, UrlSerializer} from './url_tree';
import {flatten} from './utils/collection';
@@ -55,7 +56,7 @@ export const ROUTER_PROVIDERS: Provider[] = [
useFactory: setupRouter,
deps: [
ApplicationRef, UrlSerializer, RouterOutletMap, Location, Injector, NgModuleFactoryLoader,
- Compiler, ROUTES, ROUTER_CONFIGURATION
+ Compiler, ROUTES, ROUTER_CONFIGURATION, [UrlHandlingStrategy, new Optional()]
]
},
RouterOutletMap, {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
@@ -236,24 +237,28 @@ export interface ExtraOptions {
export function setupRouter(
ref: ApplicationRef, urlSerializer: UrlSerializer, outletMap: RouterOutletMap,
location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler,
- config: Route[][], opts: ExtraOptions = {}) {
- const r = new Router(
+ config: Route[][], opts: ExtraOptions = {}, urlHandlingStrategy?: UrlHandlingStrategy) {
+ const router = new Router(
null, urlSerializer, outletMap, location, injector, loader, compiler, flatten(config));
+ if (urlHandlingStrategy) {
+ router.urlHandlingStrategy = urlHandlingStrategy;
+ }
+
if (opts.errorHandler) {
- r.errorHandler = opts.errorHandler;
+ router.errorHandler = opts.errorHandler;
}
if (opts.enableTracing) {
- r.events.subscribe(e => {
+ router.events.subscribe(e => {
console.group(`Router Event: ${(<any>e.constructor).name}`);
console.log(e.toString());
console.log(e);
console.groupEnd();
});
}
- return r;
+ return router;
}
export function rootRoute(router: Router): ActivatedRoute {
@@ -0,0 +1,46 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {UrlTree} from './url_tree';
+
+/**
+ * @whatItDoes Provides a way to migrate Angular 1 applications to Angular 2.
+ *
+ * @experimental
+ */
+export abstract class UrlHandlingStrategy {
+ /**
+ * Tells the router if this URL should be processed.
+ *
+ * When it returns true, the router will execute the regular navigation.
+ * When it returns false, the router will set the router state to an empty state.
+ * As a result, all the active components will be destroyed.
+ *
+ */
+ abstract shouldProcessUrl(url: UrlTree): boolean;
+
+ /**
+ * Extracts the part of the URL that should be handled by the router.
+ * The rest of the URL will remain untouched.
+ */
+ abstract extract(url: UrlTree): UrlTree;
+
+ /**
+ * Merges the URL fragment with the rest of the URL.
+ */
+ abstract merge(newUrlPart: UrlTree, rawUrl: UrlTree): UrlTree;
+}
+
+/**
+ * @experimental
+ */
+export class DefaultUrlHandlingStrategy implements UrlHandlingStrategy {
+ shouldProcessUrl(url: UrlTree): boolean { return true; }
+ extract(url: UrlTree): UrlTree { return url; }
+ merge(newUrlPart: UrlTree, wholeUrl: UrlTree): UrlTree { return newUrlPart; }
+}
Oops, something went wrong.

0 comments on commit 8b9ab44

Please sign in to comment.