chore(routing): update routing package

This commit is contained in:
Dan Bucholtz
2018-02-06 00:13:41 -06:00
parent f1719cd8d0
commit 97baabde93
96 changed files with 39228 additions and 346 deletions

View File

@ -11,7 +11,7 @@ export { VirtualFooter } from './directives/virtual-footer';
export { IonNav } from './nav/ion-nav';
export { AsyncActivateRoutes } from './nav/router/async-activated-routes';
export { OutletInjector } from './nav/router/outlet-injector';
export { ExtendedRouter } from './nav/router/router-extension';
export { CustomRouter } from './nav/router/router';
export { IonicRouterModule } from './nav/nav-module';
/* Providers */

View File

@ -20,7 +20,7 @@ import {
ActivatedRoute,
ChildrenOutletContexts,
Router
} from '@danbucholtz/ng-router';
} from '@angular/router';
import { FrameworkDelegate } from '@ionic/core';

View File

@ -24,12 +24,12 @@ import {
RouteReuseStrategy,
UrlHandlingStrategy,
UrlSerializer
} from '@danbucholtz/ng-router';
} from '@angular/router';
import { IonicAngularModule } from '../module';
import { PushPopOutletContexts } from './router/push-pop-outlet-contexts';
import { ExtendedRouter } from './router/router-extension';
import { CustomRouter } from './router/router';
import { IonNav } from './ion-nav';
import { flatten } from '../util/util';
@ -73,7 +73,7 @@ export function setupRouter(
config: Route[][], opts: ExtraOptions = {}, urlHandlingStrategy?: UrlHandlingStrategy,
routeReuseStrategy?: RouteReuseStrategy) {
const router = new ExtendedRouter(
const router = new CustomRouter(
null, urlSerializer, contexts, location, injector, loader, compiler, flatten(config));
if (urlHandlingStrategy) {

View File

@ -11,11 +11,14 @@ import {
Event,
RouteReuseStrategy,
RouterState,
TreeNode,
} from '@angular/router';
import {
advanceActivatedRoute,
forEach,
nodeChildrenAsMap,
} from '@danbucholtz/ng-router';
TreeNode
} from './router-utils';
export class AsyncActivateRoutes {
constructor(
@ -44,7 +47,7 @@ export class AsyncActivateRoutes {
const children: {[outletName: string]: TreeNode<ActivatedRoute>} = nodeChildrenAsMap(currNode);
const promises = futureNode.children.map(futureChild => {
const promises = futureNode.children.map((futureChild: TreeNode<ActivatedRoute>) => {
const childOutletName = futureChild.value.outlet;
const promise = this.deactivateRoutes(futureChild, children[childOutletName], contexts);
promise
@ -164,7 +167,7 @@ export class AsyncActivateRoutes {
const children: {[outlet: string]: any} = nodeChildrenAsMap(currNode);
const promises = futureNode.children.map(c => {
const promises = futureNode.children.map((c: TreeNode<ActivatedRoute>) => {
const promise = this.activateRoutes(c, children[c.value.outlet], contexts);
promise
.then(
@ -270,4 +273,4 @@ export class AsyncActivateRoutes {
return null;
}
}
}

View File

@ -2,7 +2,7 @@ import { Injector } from '@angular/core';
import {
ActivatedRoute,
ChildrenOutletContexts
} from '@danbucholtz/ng-router';
} from '@angular/router';
export class OutletInjector implements Injector {

View File

@ -8,7 +8,7 @@ import {
ChildrenOutletContexts,
OutletContext,
RouterOutlet
} from '@danbucholtz/ng-router';
} from '@angular/router';
export class PushPopOutletContexts extends ChildrenOutletContexts {

View File

@ -1,226 +0,0 @@
import {
Event,
GuardsCheckEnd,
GuardsCheckStart,
NavigationCancel,
NavigationEnd,
NavigationError,
PreActivation,
ResolveEnd,
ResolveStart,
Router,
RouterState,
RouterStateSnapshot,
RoutesRecognized,
UrlTree,
applyRedirects,
createRouterState,
isNavigationCancelingError,
recognize
} from '@danbucholtz/ng-router';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import { of } from 'rxjs/observable/of';
import { concatMap } from 'rxjs/operator/concatMap';
import { map } from 'rxjs/operator/map';
import { mergeMap } from 'rxjs/operator/mergeMap';
import { AsyncActivateRoutes } from './async-activated-routes';
export class ExtendedRouter extends Router {
protected runNavigate(
url: UrlTree, rawUrl: UrlTree, skipLocationChange: boolean, replaceUrl: boolean, id: number,
precreatedState: RouterStateSnapshot|null): Promise<boolean> {
if (id !== this.navigationId) {
(this.events as Subject<Event>)
.next(new NavigationCancel(
id, this.serializeUrl(url),
`Navigation ID ${id} is not equal to the current navigation id ${this.navigationId}`));
return Promise.resolve(false);
}
return new Promise((resolvePromise, rejectPromise) => {
// create an observable of the url and route state snapshot
// this operation do not result in any side effects
let urlAndSnapshot$: Observable<{appliedUrl: UrlTree, snapshot: RouterStateSnapshot}>;
if (!precreatedState) {
const moduleInjector = this.ngModule.injector;
const redirectsApplied$ =
applyRedirects(moduleInjector, this.configLoader, this.urlSerializer, url, this.config);
urlAndSnapshot$ = mergeMap.call(redirectsApplied$, (appliedUrl: UrlTree) => {
return map.call(
recognize(
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl),
this.paramsInheritanceStrategy),
(snapshot: any) => {
(this.events as Subject<Event>)
.next(new RoutesRecognized(
id, this.serializeUrl(url), this.serializeUrl(appliedUrl), snapshot));
return {appliedUrl, snapshot};
});
});
} else {
urlAndSnapshot$ = of ({appliedUrl: url, snapshot: precreatedState});
}
const beforePreactivationDone$ = mergeMap.call(
urlAndSnapshot$, (p: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
return map.call(this.hooks.beforePreactivation(p.snapshot), () => p);
});
// run preactivation: guards and data resolvers
let preActivation: PreActivation;
const preactivationSetup$ = map.call(
beforePreactivationDone$,
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
const moduleInjector = this.ngModule.injector;
preActivation = new PreActivation(
snapshot, this.routerState.snapshot, moduleInjector,
(evt: Event) => this.triggerEvent(evt));
preActivation.initialize(this.rootContexts);
return {appliedUrl, snapshot};
});
const preactivationCheckGuards$ = mergeMap.call(
preactivationSetup$,
({appliedUrl, snapshot}: {appliedUrl: string, snapshot: RouterStateSnapshot}) => {
if (this.navigationId !== id) return of (false);
this.triggerEvent(
new GuardsCheckStart(id, this.serializeUrl(url), appliedUrl, snapshot));
return map.call(preActivation.checkGuards(), (shouldActivate: boolean) => {
this.triggerEvent(new GuardsCheckEnd(
id, this.serializeUrl(url), appliedUrl, snapshot, shouldActivate));
return {appliedUrl: appliedUrl, snapshot: snapshot, shouldActivate: shouldActivate};
});
});
const preactivationResolveData$ = mergeMap.call(
preactivationCheckGuards$,
(p: {appliedUrl: string, snapshot: RouterStateSnapshot, shouldActivate: boolean}) => {
if (this.navigationId !== id) return of (false);
if (p.shouldActivate && preActivation.isActivating()) {
this.triggerEvent(
new ResolveStart(id, this.serializeUrl(url), p.appliedUrl, p.snapshot));
return map.call(preActivation.resolveData(this.paramsInheritanceStrategy), () => {
this.triggerEvent(
new ResolveEnd(id, this.serializeUrl(url), p.appliedUrl, p.snapshot));
return p;
});
} else {
return of (p);
}
});
const preactivationDone$ = mergeMap.call(preactivationResolveData$, (p: any) => {
return map.call(this.hooks.afterPreactivation(p.snapshot), () => p);
});
// create router state
// this operation has side effects => route state is being affected
const routerState$ =
map.call(preactivationDone$, ({appliedUrl, snapshot, shouldActivate}: any) => {
if (shouldActivate) {
const state = createRouterState(this.routeReuseStrategy, snapshot, this.routerState);
return {appliedUrl, state, shouldActivate};
} else {
return {appliedUrl, state: null, shouldActivate};
}
});
// applied the new router state
// this operation has side effects
let navigationIsSuccessful = false;
const storedState = this.routerState;
const storedUrl = this.currentUrlTree;
const activatedRoutes: AsyncActivateRoutes[] = [];
routerState$
.forEach(({appliedUrl, state, shouldActivate}: any) => {
if (!shouldActivate || id !== this.navigationId) {
navigationIsSuccessful = false;
return;
}
this.currentUrlTree = appliedUrl;
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, rawUrl);
(this as{routerState: RouterState}).routerState = state;
if (!skipLocationChange) {
const path = this.urlSerializer.serialize(this.rawUrlTree);
if (this.location.isCurrentPathEqualTo(path) || replaceUrl) {
// this.location.replaceState(path, '', {navigationId: id});
this.location.replaceState(path, '');
} else {
// this.location.go(path, '', {navigationId: id});
this.location.go(path, '');
}
}
activatedRoutes.push(new AsyncActivateRoutes(this.routeReuseStrategy, state, storedState, (evt: Event) => this.triggerEvent(evt)))
})
.then(() => {
const promises = activatedRoutes.map(activatedRoute => activatedRoute.activate(this.rootContexts));
return Promise.all(promises)
.then(
() => {
navigationIsSuccessful = true;
}
);
})
.then(
() => {
if (navigationIsSuccessful) {
this.navigated = true;
this.lastSuccessfulId = id;
(this.events as Subject<Event>)
.next(new NavigationEnd(
id, this.serializeUrl(url), this.serializeUrl(this.currentUrlTree)));
resolvePromise(true);
} else {
this.resetUrlToCurrentUrlTree();
(this.events as Subject<Event>)
.next(new NavigationCancel(id, this.serializeUrl(url), ''));
resolvePromise(false);
}
},
(e: any) => {
if (isNavigationCancelingError(e)) {
this.navigated = true;
this.resetStateAndUrl(storedState, storedUrl, rawUrl);
(this.events as Subject<Event>)
.next(new NavigationCancel(id, this.serializeUrl(url), e.message));
resolvePromise(false);
} else {
this.resetStateAndUrl(storedState, storedUrl, rawUrl);
(this.events as Subject<Event>)
.next(new NavigationError(id, this.serializeUrl(url), e));
try {
resolvePromise(this.errorHandler(e));
} catch (ee) {
rejectPromise(ee);
}
}
});
});
}
triggerEvent(e: Event): void { ((this.events as any)as Subject<Event>).next(e); }
}

View File

@ -0,0 +1,84 @@
export function nodeChildrenAsMap<T extends{outlet: string}>(node: TreeNode<T>| null) {
const map: {[outlet: string]: TreeNode<T>} = {};
if (node) {
node.children.forEach(child => map[child.value.outlet] = child);
}
return map;
}
export function forEach<K, V>(map: {[key: string]: V}, callback: (v: V, k: string) => void): void {
for (const prop in map) {
if (map.hasOwnProperty(prop)) {
callback(map[prop], prop);
}
}
}
export class TreeNode<T> {
constructor(public value: T, public children: TreeNode<T>[]) {}
toString(): string { return `TreeNode(${this.value})`; }
}
/**
* The expectation is that the activate route is created with the right set of parameters.
* So we push new values into the observables only when they are not the initial values.
* And we detect that by checking if the snapshot field is set.
*/
export function advanceActivatedRoute(route: any): void {
if (route.snapshot) {
const currentSnapshot = route.snapshot;
const nextSnapshot = (route as any)._futureSnapshot;
route.snapshot = nextSnapshot;
if (!shallowEqual(currentSnapshot.queryParams, nextSnapshot.queryParams)) {
(<any>route.queryParams).next(nextSnapshot.queryParams);
}
if (currentSnapshot.fragment !== nextSnapshot.fragment) {
(<any>route.fragment).next(nextSnapshot.fragment);
}
if (!shallowEqual(currentSnapshot.params, nextSnapshot.params)) {
(<any>route.params).next(nextSnapshot.params);
}
if (!shallowEqualArrays(currentSnapshot.url, nextSnapshot.url)) {
(<any>route.url).next(nextSnapshot.url);
}
if (!shallowEqual(currentSnapshot.data, nextSnapshot.data)) {
(<any>route.data).next(nextSnapshot.data);
}
} else {
route.snapshot = (route as any)._futureSnapshot;
// this is for resolved data
(<any>route.data).next((route as any)._futureSnapshot.data);
}
}
export function shallowEqualArrays(a: any[], b: any[]): boolean {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; ++i) {
if (!shallowEqual(a[i], b[i])) return false;
}
return true;
}
export function shallowEqual(a: {[x: string]: any}, b: {[x: string]: any}): boolean {
const k1 = Object.keys(a);
const k2 = Object.keys(b);
if (k1.length != k2.length) {
return false;
}
let key: string;
for (let i = 0; i < k1.length; i++) {
key = k1[i];
if (a[key] !== b[key]) {
return false;
}
}
return true;
}
export function flatten<T>(arr: T[][]): T[] {
return Array.prototype.concat.apply([], arr);
}

View File

@ -0,0 +1,113 @@
import {
Event,
NavigationCancel,
NavigationEnd,
NavigationError,
Router,
RouterState,
UrlTree
} from '@angular/router';
import { Observable } from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import { AsyncActivateRoutes } from './async-activated-routes';
export class CustomRouter extends Router {
protected activateRoutes(state: Observable<{appliedUrl: string, state: RouterState, shouldActivate: boolean}>, storedState: RouterState,
storedUrl: UrlTree, id: number, url: UrlTree, rawUrl: UrlTree, skipLocationChange: boolean, replaceUrl: boolean, resolvePromise: any, rejectPromise: any) {
console.log('custom activateRoutes!!!!1');
// applied the new router state
// this operation has side effects
let navigationIsSuccessful: boolean;
const routes: AsyncActivateRoutes[] = [];
state
.forEach(({appliedUrl, state, shouldActivate}: any) => {
if (!shouldActivate || id !== this.navigationId) {
navigationIsSuccessful = false;
return;
}
this.currentUrlTree = appliedUrl;
this.rawUrlTree = this.urlHandlingStrategy.merge(this.currentUrlTree, rawUrl);
(this as{routerState: RouterState}).routerState = state;
if (!skipLocationChange) {
const path = this.urlSerializer.serialize(this.rawUrlTree);
if (this.location.isCurrentPathEqualTo(path) || replaceUrl) {
this.location.replaceState(path, '');
} else {
this.location.go(path, '');
}
}
routes.push(new AsyncActivateRoutes(this.routeReuseStrategy, state, storedState, (evt: Event) => this.triggerEvent(evt)))
})
.then(
() => {
const promises = routes.map(activatedRoute => activatedRoute.activate(this.rootContexts));
return Promise.all(promises)
.then(
() => {
navigationIsSuccessful = true;
}
);
}
)
.then(
() => {
if (navigationIsSuccessful) {
this.navigated = true;
this.lastSuccessfulId = id;
(this.events as Subject<Event>)
.next(new NavigationEnd(
id, this.serializeUrl(url), this.serializeUrl(this.currentUrlTree)));
resolvePromise(true);
} else {
this.resetUrlToCurrentUrlTree();
(this.events as Subject<Event>)
.next(new NavigationCancel(id, this.serializeUrl(url), ''));
resolvePromise(false);
}
},
(e: any) => {
if (isNavigationCancelingError(e)) {
this.navigated = true;
this.resetStateAndUrl(storedState, storedUrl, rawUrl);
(this.events as Subject<Event>)
.next(new NavigationCancel(id, this.serializeUrl(url), e.message));
resolvePromise(false);
} else {
this.resetStateAndUrl(storedState, storedUrl, rawUrl);
(this.events as any as Subject<Event>)
.next(new NavigationError(id, this.serializeUrl(url), e));
try {
resolvePromise(this.errorHandler(e));
} catch (ee) {
rejectPromise(ee);
}
}
});
}
triggerEvent(e: Event): void { (this.events as any as Subject<Event>).next(e); }
serializeUrl(url: UrlTree): string { return this.urlSerializer.serialize(url); }
}
const NAVIGATION_CANCELING_ERROR = 'ngNavigationCancelingError';
export function isNavigationCancelingError(error: Error) {
return error && (error as any)[NAVIGATION_CANCELING_ERROR];
}

View File

@ -1,4 +1,4 @@
import { NavContainer } from '@ionic/core';
import { PublicNav } from '@ionic/core';
export class App {
@ -15,15 +15,15 @@ export class App {
return isScrollingImpl(this);
}
getRootNavs(): NavContainer[] {
getRootNavs(): PublicNav[] {
return getRootNavsImpl(this);
}
getActiveNavs(rootNavId?: number): NavContainer[] {
return getActiveNavsImpl(this, rootNavId);
getTopNavs(rootNavId?: number): PublicNav[] {
return getTopNavsImpl(this, rootNavId);
}
getNavByIdOrName(nameOrId: number | string): NavContainer {
getNavByIdOrName(nameOrId: number | string): PublicNav {
return getNavByIdOrNameImpl(this, nameOrId);
}
}
@ -42,14 +42,14 @@ export function getRootNavsImpl(app: App) {
return [];
}
export function getActiveNavsImpl(app: App, rootNavId?: number): NavContainer[] {
if (app._element && app._element.getActiveNavs) {
return app._element.getActiveNavs(rootNavId);
export function getTopNavsImpl(app: App, rootNavId?: number): PublicNav[] {
if (app._element && app._element.getTopNavs) {
return app._element.getTopNavs(rootNavId);
}
return [];
}
export function getNavByIdOrNameImpl(app: App, nameOrId: number | string): NavContainer {
export function getNavByIdOrNameImpl(app: App, nameOrId: number | string): PublicNav {
if (app._element && app._element.getNavByIdOrName) {
return app._element.getNavByIdOrName(nameOrId);
}

View File

@ -93,13 +93,27 @@ export class NavController implements PublicNav {
return false;
}
getFirstView(): PublicViewController {
if (this.element.getFirstView) {
return this.element.getFirstView();
first(): PublicViewController {
if (this.element.first) {
return this.element.first();
}
return null;
}
last(): PublicViewController {
if (this.element.last) {
return this.element.last();
}
return null;
}
getViews(): PublicViewController[] {
if (this.element.getViews) {
return this.element.getViews();
}
return [];
}
getChildNavs(): PublicNav[] {
if (this.element.getChildNavs) {
return this.element.getChildNavs();

View File

@ -4,7 +4,7 @@ import {
ComponentRef,
Injector
} from '@angular/core';
import { ActivatedRoute } from '@danbucholtz/ng-router';
import { ActivatedRoute } from '@angular/router';
import { FrameworkMountingData } from '@ionic/core';