Skip to content

Commit 8b9ab44

Browse files
vsavkinalxhub
authored andcommitted
feat(router): add support for ng1/ng2 migration (angular#12160)
1 parent b0a03fc commit 8b9ab44

File tree

8 files changed

+321
-55
lines changed

8 files changed

+321
-55
lines changed

modules/@angular/router/src/create_router_state.ts

-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ function createNode(curr: TreeNode<ActivatedRouteSnapshot>, prevState?: TreeNode
2121
if (prevState && equalRouteSnapshots(prevState.value.snapshot, curr.value)) {
2222
const value = prevState.value;
2323
value._futureSnapshot = curr.value;
24-
2524
const children = createOrReuseChildren(curr, prevState);
2625
return new TreeNode<ActivatedRoute>(value, children);
2726

modules/@angular/router/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export {RouterOutletMap} from './router_outlet_map';
1818
export {NoPreloading, PreloadAllModules, PreloadingStrategy} from './router_preloader';
1919
export {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot} from './router_state';
2020
export {PRIMARY_OUTLET, Params} from './shared';
21-
export {DefaultUrlSerializer, UrlSegment, UrlSerializer, UrlTree} from './url_tree';
21+
export {UrlHandlingStrategy} from './url_handling_strategy';
22+
export {DefaultUrlSerializer, UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
2223

2324
export * from './private_export'

modules/@angular/router/src/router.ts

+85-35
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
3030
import {RouterOutletMap} from './router_outlet_map';
3131
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
3232
import {NavigationCancelingError, PRIMARY_OUTLET, Params} from './shared';
33+
import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
3334
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
3435
import {andObservables, forEach, merge, shallowEqual, waitForMap, wrapIntoObservable} from './utils/collection';
3536
import {TreeNode} from './utils/tree';
@@ -285,6 +286,9 @@ function defaultErrorHandler(error: any): any {
285286
*/
286287
export class Router {
287288
private currentUrlTree: UrlTree;
289+
private rawUrlTree: UrlTree;
290+
private lastNavigation: UrlTree;
291+
288292
private currentRouterState: RouterState;
289293
private locationSubscription: Subscription;
290294
private routerEvents: Subject<Event>;
@@ -303,6 +307,11 @@ export class Router {
303307
*/
304308
navigated: boolean = false;
305309

310+
/**
311+
* Extracts and merges URLs. Used for Angular 1 to Angular 2 migrations.
312+
*/
313+
urlHandlingStrategy: UrlHandlingStrategy = new DefaultUrlHandlingStrategy();
314+
306315
/**
307316
* Creates the router service.
308317
*/
@@ -314,6 +323,7 @@ export class Router {
314323
this.resetConfig(config);
315324
this.routerEvents = new Subject<Event>();
316325
this.currentUrlTree = createEmptyUrlTree();
326+
this.rawUrlTree = this.currentUrlTree;
317327
this.configLoader = new RouterConfigLoader(loader, compiler);
318328
this.currentRouterState = createEmptyState(this.currentUrlTree, this.rootComponentType);
319329
}
@@ -344,12 +354,20 @@ export class Router {
344354
// Zone.current.wrap is needed because of the issue with RxJS scheduler,
345355
// which does not work properly with zone.js in IE and Safari
346356
this.locationSubscription = <any>this.location.subscribe(Zone.current.wrap((change: any) => {
347-
const tree = this.urlSerializer.parse(change['url']);
348-
// we fire multiple events for a single URL change
349-
// we should navigate only once
350-
return this.currentUrlTree.toString() !== tree.toString() ?
351-
this.scheduleNavigation(tree, {skipLocationChange: change['pop'], replaceUrl: true}) :
352-
null;
357+
const rawUrlTree = this.urlSerializer.parse(change['url']);
358+
const tree = this.urlHandlingStrategy.extract(rawUrlTree);
359+
360+
361+
setTimeout(() => {
362+
// we fire multiple events for a single URL change
363+
// we should navigate only once
364+
if (!this.lastNavigation || this.lastNavigation.toString() !== tree.toString()) {
365+
this.scheduleNavigation(
366+
rawUrlTree, tree, {skipLocationChange: change['pop'], replaceUrl: true});
367+
} else {
368+
this.rawUrlTree = rawUrlTree;
369+
}
370+
}, 0);
353371
}));
354372
}
355373

@@ -470,10 +488,10 @@ export class Router {
470488
navigateByUrl(url: string|UrlTree, extras: NavigationExtras = {skipLocationChange: false}):
471489
Promise<boolean> {
472490
if (url instanceof UrlTree) {
473-
return this.scheduleNavigation(url, extras);
491+
return this.scheduleNavigation(this.rawUrlTree, url, extras);
474492
} else {
475493
const urlTree = this.urlSerializer.parse(url);
476-
return this.scheduleNavigation(urlTree, extras);
494+
return this.scheduleNavigation(this.rawUrlTree, urlTree, extras);
477495
}
478496
}
479497

@@ -500,7 +518,7 @@ export class Router {
500518
*/
501519
navigate(commands: any[], extras: NavigationExtras = {skipLocationChange: false}):
502520
Promise<boolean> {
503-
return this.scheduleNavigation(this.createUrlTree(commands, extras), extras);
521+
return this.scheduleNavigation(this.rawUrlTree, this.createUrlTree(commands, extras), extras);
504522
}
505523

506524
/**
@@ -525,16 +543,34 @@ export class Router {
525543
}
526544
}
527545

528-
private scheduleNavigation(url: UrlTree, extras: NavigationExtras): Promise<boolean> {
529-
const id = ++this.navigationId;
530-
this.routerEvents.next(new NavigationStart(id, this.serializeUrl(url)));
531-
return Promise.resolve().then(
532-
(_) => this.runNavigate(url, extras.skipLocationChange, extras.replaceUrl, id));
546+
private scheduleNavigation(rawUrl: UrlTree, url: UrlTree, extras: NavigationExtras):
547+
Promise<boolean> {
548+
if (this.urlHandlingStrategy.shouldProcessUrl(url)) {
549+
const id = ++this.navigationId;
550+
this.routerEvents.next(new NavigationStart(id, this.serializeUrl(url)));
551+
552+
return Promise.resolve().then(
553+
(_) => this.runNavigate(
554+
rawUrl, url, extras.skipLocationChange, extras.replaceUrl, id, null));
555+
556+
// we cannot process the current URL, but we could process the previous one =>
557+
// we need to do some cleanup
558+
} else if (this.urlHandlingStrategy.shouldProcessUrl(this.rawUrlTree)) {
559+
const id = ++this.navigationId;
560+
this.routerEvents.next(new NavigationStart(id, this.serializeUrl(url)));
561+
562+
return Promise.resolve().then(
563+
(_) => this.runNavigate(
564+
rawUrl, url, false, false, id, createEmptyState(url, this.rootComponentType)));
565+
} else {
566+
this.rawUrlTree = rawUrl;
567+
return Promise.resolve(null);
568+
}
533569
}
534570

535571
private runNavigate(
536-
url: UrlTree, shouldPreventPushState: boolean, shouldReplaceUrl: boolean,
537-
id: number): Promise<boolean> {
572+
rawUrl: UrlTree, url: UrlTree, shouldPreventPushState: boolean, shouldReplaceUrl: boolean,
573+
id: number, precreatedState: RouterState): Promise<boolean> {
538574
if (id !== this.navigationId) {
539575
this.location.go(this.urlSerializer.serialize(this.currentUrlTree));
540576
this.routerEvents.next(new NavigationCancel(
@@ -553,23 +589,33 @@ export class Router {
553589
const storedState = this.currentRouterState;
554590
const storedUrl = this.currentUrlTree;
555591

556-
const redirectsApplied$ = applyRedirects(this.injector, this.configLoader, url, this.config);
557-
558-
const snapshot$ = mergeMap.call(redirectsApplied$, (u: UrlTree) => {
559-
appliedUrl = u;
560-
return recognize(
561-
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl));
562-
});
563-
564-
const emitRecognzied$ = map.call(snapshot$, (newRouterStateSnapshot: RouterStateSnapshot) => {
565-
this.routerEvents.next(new RoutesRecognized(
566-
id, this.serializeUrl(url), this.serializeUrl(appliedUrl), newRouterStateSnapshot));
567-
return newRouterStateSnapshot;
568-
});
569-
570-
const routerState$ = map.call(emitRecognzied$, (routerStateSnapshot: RouterStateSnapshot) => {
571-
return createRouterState(routerStateSnapshot, this.currentRouterState);
572-
});
592+
let routerState$: any;
593+
594+
if (!precreatedState) {
595+
const redirectsApplied$ =
596+
applyRedirects(this.injector, this.configLoader, url, this.config);
597+
598+
const snapshot$ = mergeMap.call(redirectsApplied$, (u: UrlTree) => {
599+
appliedUrl = u;
600+
return recognize(
601+
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl));
602+
});
603+
604+
const emitRecognzied$ =
605+
map.call(snapshot$, (newRouterStateSnapshot: RouterStateSnapshot) => {
606+
this.routerEvents.next(new RoutesRecognized(
607+
id, this.serializeUrl(url), this.serializeUrl(appliedUrl),
608+
newRouterStateSnapshot));
609+
return newRouterStateSnapshot;
610+
});
611+
612+
routerState$ = map.call(emitRecognzied$, (routerStateSnapshot: RouterStateSnapshot) => {
613+
return createRouterState(routerStateSnapshot, this.currentRouterState);
614+
});
615+
} else {
616+
appliedUrl = url;
617+
routerState$ = of (precreatedState);
618+
}
573619

574620
const preactivation$ = map.call(routerState$, (newState: RouterState) => {
575621
state = newState;
@@ -595,11 +641,14 @@ export class Router {
595641
return;
596642
}
597643

644+
this.lastNavigation = appliedUrl;
598645
this.currentUrlTree = appliedUrl;
646+
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, rawUrl);
647+
599648
this.currentRouterState = state;
600649

601650
if (!shouldPreventPushState) {
602-
let path = this.urlSerializer.serialize(appliedUrl);
651+
let path = this.urlSerializer.serialize(this.rawUrlTree);
603652
if (this.location.isCurrentPathEqualTo(path) || shouldReplaceUrl) {
604653
this.location.replaceState(path);
605654
} else {
@@ -641,7 +690,8 @@ export class Router {
641690
if (id === this.navigationId) {
642691
this.currentRouterState = storedState;
643692
this.currentUrlTree = storedUrl;
644-
this.location.replaceState(this.serializeUrl(storedUrl));
693+
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, rawUrl);
694+
this.location.replaceState(this.serializeUrl(this.rawUrlTree));
645695
}
646696
});
647697
});

modules/@angular/router/src/router_module.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {ROUTES} from './router_config_loader';
1818
import {RouterOutletMap} from './router_outlet_map';
1919
import {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
2020
import {ActivatedRoute} from './router_state';
21+
import {UrlHandlingStrategy} from './url_handling_strategy';
2122
import {DefaultUrlSerializer, UrlSerializer} from './url_tree';
2223
import {flatten} from './utils/collection';
2324

@@ -55,7 +56,7 @@ export const ROUTER_PROVIDERS: Provider[] = [
5556
useFactory: setupRouter,
5657
deps: [
5758
ApplicationRef, UrlSerializer, RouterOutletMap, Location, Injector, NgModuleFactoryLoader,
58-
Compiler, ROUTES, ROUTER_CONFIGURATION
59+
Compiler, ROUTES, ROUTER_CONFIGURATION, [UrlHandlingStrategy, new Optional()]
5960
]
6061
},
6162
RouterOutletMap, {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
@@ -236,24 +237,28 @@ export interface ExtraOptions {
236237
export function setupRouter(
237238
ref: ApplicationRef, urlSerializer: UrlSerializer, outletMap: RouterOutletMap,
238239
location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler,
239-
config: Route[][], opts: ExtraOptions = {}) {
240-
const r = new Router(
240+
config: Route[][], opts: ExtraOptions = {}, urlHandlingStrategy?: UrlHandlingStrategy) {
241+
const router = new Router(
241242
null, urlSerializer, outletMap, location, injector, loader, compiler, flatten(config));
242243

244+
if (urlHandlingStrategy) {
245+
router.urlHandlingStrategy = urlHandlingStrategy;
246+
}
247+
243248
if (opts.errorHandler) {
244-
r.errorHandler = opts.errorHandler;
249+
router.errorHandler = opts.errorHandler;
245250
}
246251

247252
if (opts.enableTracing) {
248-
r.events.subscribe(e => {
253+
router.events.subscribe(e => {
249254
console.group(`Router Event: ${(<any>e.constructor).name}`);
250255
console.log(e.toString());
251256
console.log(e);
252257
console.groupEnd();
253258
});
254259
}
255260

256-
return r;
261+
return router;
257262
}
258263

259264
export function rootRoute(router: Router): ActivatedRoute {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {UrlTree} from './url_tree';
10+
11+
/**
12+
* @whatItDoes Provides a way to migrate Angular 1 applications to Angular 2.
13+
*
14+
* @experimental
15+
*/
16+
export abstract class UrlHandlingStrategy {
17+
/**
18+
* Tells the router if this URL should be processed.
19+
*
20+
* When it returns true, the router will execute the regular navigation.
21+
* When it returns false, the router will set the router state to an empty state.
22+
* As a result, all the active components will be destroyed.
23+
*
24+
*/
25+
abstract shouldProcessUrl(url: UrlTree): boolean;
26+
27+
/**
28+
* Extracts the part of the URL that should be handled by the router.
29+
* The rest of the URL will remain untouched.
30+
*/
31+
abstract extract(url: UrlTree): UrlTree;
32+
33+
/**
34+
* Merges the URL fragment with the rest of the URL.
35+
*/
36+
abstract merge(newUrlPart: UrlTree, rawUrl: UrlTree): UrlTree;
37+
}
38+
39+
/**
40+
* @experimental
41+
*/
42+
export class DefaultUrlHandlingStrategy implements UrlHandlingStrategy {
43+
shouldProcessUrl(url: UrlTree): boolean { return true; }
44+
extract(url: UrlTree): UrlTree { return url; }
45+
merge(newUrlPart: UrlTree, wholeUrl: UrlTree): UrlTree { return newUrlPart; }
46+
}

0 commit comments

Comments
 (0)