diff --git a/angular/src/directives/navigation/ion-router-outlet.ts b/angular/src/directives/navigation/ion-router-outlet.ts index f50095f177..85953f64f4 100644 --- a/angular/src/directives/navigation/ion-router-outlet.ts +++ b/angular/src/directives/navigation/ion-router-outlet.ts @@ -1,6 +1,6 @@ import { Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Injector, Input, OnDestroy, OnInit, Optional, Output, ViewContainerRef } from '@angular/core'; -import { ActivatedRoute, ChildrenOutletContexts, PRIMARY_OUTLET, Router } from '@angular/router'; -import { StackController } from './router-controller'; +import { ActivatedRoute, ChildrenOutletContexts, OutletContext, PRIMARY_OUTLET, Router } from '@angular/router'; +import { StackController, RouteView } from './router-controller'; import { NavController } from '../../providers/nav-controller'; import { bindLifecycleEvents } from '../../providers/angular-delegate'; @@ -11,6 +11,7 @@ import { bindLifecycleEvents } from '../../providers/angular-delegate'; export class IonRouterOutlet implements OnDestroy, OnInit { private activated: ComponentRef | null = null; + private activatedView: RouteView | null = null; private _activatedRoute: ActivatedRoute | null = null; private name: string; @@ -42,22 +43,21 @@ export class IonRouterOutlet implements OnDestroy, OnInit { } ngOnDestroy(): void { + console.log('router-outlet destroyed'); this.parentContexts.onChildOutletDestroyed(this.name); } + getContext(): OutletContext | null { + return this.parentContexts.getContext(this.name); + } + ngOnInit(): void { if (!this.activated) { // If the outlet was not instantiated at the time the route got activated we need to populate // the outlet when it is initialized (ie inside a NgIf) - const context = this.parentContexts.getContext(this.name); + const context = this.getContext(); if (context && context.route) { - if (context.attachRef) { - // `attachRef` is populated when there is an existing component to mount - this.attach(context.attachRef, context.route); - } else { - // otherwise the component defined in the configuration is created - this.activateWith(context.route, context.resolver || null); - } + this.activateWith(context.route, context.resolver || null); } } } @@ -89,43 +89,45 @@ export class IonRouterOutlet implements OnDestroy, OnInit { * Called when the `RouteReuseStrategy` instructs to detach the subtree */ detach(): ComponentRef { - if (!this.activated) { - throw new Error('Outlet is not activated'); - } - this.location.detach(); - const cmp = this.activated; - this.activated = null; - this._activatedRoute = null; - return cmp; + throw new Error('incompatible reuse strategy'); } /** * Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree */ - attach(ref: ComponentRef, activatedRoute: ActivatedRoute) { - this.activated = ref; - this._activatedRoute = activatedRoute; - this.location.insert(ref.hostView); + attach(_ref: ComponentRef, _activatedRoute: ActivatedRoute) { + throw new Error('incompatible reuse strategy'); } deactivate(): void { if (this.activated) { + if (this.activatedView) { + this.activatedView.savedData = new Map(this.getContext()!.children['contexts']); + } const c = this.component; + this.activatedView = null; this.activated = null; this._activatedRoute = null; this.deactivateEvents.emit(c); } } - async activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null) { + activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null) { if (this.isActivated) { throw new Error('Cannot activate an already activated outlet'); } this._activatedRoute = activatedRoute; + let cmpRef: any; let enteringView = this.stackCtrl.getExistingView(activatedRoute); if (enteringView) { - this.activated = enteringView.ref; + cmpRef = this.activated = enteringView.ref; + const saved = enteringView.savedData; + if (saved) { + // self-restore + const context = this.getContext()!; + context.children['contexts'] = saved; + } } else { const snapshot = (activatedRoute as any)._futureSnapshot; const component = snapshot.routeConfig!.component as any; @@ -135,9 +137,9 @@ export class IonRouterOutlet implements OnDestroy, OnInit { const childContexts = this.parentContexts.getOrCreateContext(this.name).children; const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector); - const cmp = this.activated = this.location.createComponent(factory, this.location.length, injector); + cmpRef = this.activated = this.location.createComponent(factory, this.location.length, injector); - bindLifecycleEvents(cmp.instance, cmp.location.nativeElement); + bindLifecycleEvents(cmpRef.instance, cmpRef.location.nativeElement); // Calling `markForCheck` to make sure we will run the change detection when the // `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component. @@ -146,10 +148,12 @@ export class IonRouterOutlet implements OnDestroy, OnInit { } const { direction, animated } = this.navCtrl.consumeTransition(); - await this.stackCtrl.setActive(enteringView, direction, animated); - this.activateEvents.emit(this.activated.instance); + this.activatedView = enteringView; + this.stackCtrl.setActive(enteringView, direction, animated).then(() => { + this.activateEvents.emit(cmpRef.instance); + emitEvent(this.elementRef.nativeElement); + }); - emitEvent(this.elementRef.nativeElement); } canGoBack(deep = 1) { diff --git a/angular/src/directives/navigation/router-controller.ts b/angular/src/directives/navigation/router-controller.ts index 0bf121e035..65a053213e 100644 --- a/angular/src/directives/navigation/router-controller.ts +++ b/angular/src/directives/navigation/router-controller.ts @@ -80,9 +80,11 @@ export class StackController { .forEach(view => destroyView(view)); for (let i = 0; i < views.length - 1; i++) { - const element = views[i].element; + const view = views[i]; + const element = view.element; element.setAttribute('aria-hidden', 'true'); element.classList.add('ion-page-hidden'); + // view.ref.changeDetectorRef.detach(); } this.viewsSnapshot = views.slice(); @@ -149,4 +151,5 @@ export interface RouteView { element: HTMLElement; ref: ComponentRef; deactivatedId: number; + savedData?: any; } diff --git a/angular/src/util/ionic-router-reuse-strategy.ts b/angular/src/util/ionic-router-reuse-strategy.ts index 3e94b7e59d..0b5718fe79 100644 --- a/angular/src/util/ionic-router-reuse-strategy.ts +++ b/angular/src/util/ionic-router-reuse-strategy.ts @@ -1,18 +1,19 @@ import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'; -import { deepEqual, objectValues } from './util'; export class IonicRouteStrategy implements RouteReuseStrategy { + shouldDetach(_route: ActivatedRouteSnapshot): boolean { return false; } - // tslint:disable-next-line - store(_route: ActivatedRouteSnapshot, _detachedTree: DetachedRouteHandle): void {} - shouldAttach(_route: ActivatedRouteSnapshot): boolean { return false; } + store(_route: ActivatedRouteSnapshot, _detachedTree: DetachedRouteHandle): void { + return; + } + retrieve(_route: ActivatedRouteSnapshot): DetachedRouteHandle | null { return null; } @@ -21,23 +22,29 @@ export class IonicRouteStrategy implements RouteReuseStrategy { future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot ): boolean { - // checking router params - const futureParams = objectValues(future.params); - const currParams = objectValues(curr.params); - - if ( - futureParams && - !!futureParams.length && - currParams && - currParams.length > 0 - ) { - // If the router params do not match, render the new component - return ( - deepEqual(future.params, curr.params) && - future.routeConfig === curr.routeConfig - ); - } else { - return future.routeConfig === curr.routeConfig; + if (future.routeConfig !== curr.routeConfig) { + return false; } + if (future.component !== curr.component) { + return false; + } + + // checking router params + const futureParams = future.params; + const currentParams = curr.params; + const keysA = Object.keys(futureParams); + const keysB = Object.keys(currentParams); + + if (keysA.length !== keysB.length) { + return false; + } + + // Test for A's keys different from B. + for (const key of keysA) { + if (currentParams[key] !== futureParams[key]) { + return false; + } + } + return true; } } diff --git a/angular/src/util/util.ts b/angular/src/util/util.ts index b279afeb61..513b9db84e 100644 --- a/angular/src/util/util.ts +++ b/angular/src/util/util.ts @@ -30,10 +30,6 @@ export function ensureElementInBody(elementName: string) { return element as HTMLStencilElement; } -export function objectValues(obj: any): any[] { - return Object.keys(obj).map(key => obj[key]); -} - export function deepEqual(x: any, y: any) { if (x === y) { return true; diff --git a/core/src/components/nav/view-controller.ts b/core/src/components/nav/view-controller.ts index e67982deca..e075c812d9 100644 --- a/core/src/components/nav/view-controller.ts +++ b/core/src/components/nav/view-controller.ts @@ -62,6 +62,9 @@ export function matches(view: ViewController | undefined, id: string, params: Co const currentParams = view.params; const null1 = (currentParams == null); const null2 = (params == null); + if (currentParams === params) { + return true; + } if (null1 !== null2) { return false; }