fix(angular): save internal data

fixes #14888
fixes #14885
fixes #15054
fixes #15050
This commit is contained in:
Manu Mtz.-Almeida
2018-08-16 15:47:40 +02:00
parent fef751d718
commit f84bb76481
5 changed files with 68 additions and 55 deletions

View File

@ -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<any> | null = null;
private activatedView: RouteView | null = null;
private _activatedRoute: ActivatedRoute | null = null;
private name: string;
@ -42,25 +43,24 @@ 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);
}
}
}
}
get isActivated(): boolean { return !!this.activated; }
@ -89,43 +89,45 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
* Called when the `RouteReuseStrategy` instructs to detach the subtree
*/
detach(): ComponentRef<any> {
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<any>, activatedRoute: ActivatedRoute) {
this.activated = ref;
this._activatedRoute = activatedRoute;
this.location.insert(ref.hostView);
attach(_ref: ComponentRef<any>, _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);
});
}
canGoBack(deep = 1) {

View File

@ -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<any>;
deactivatedId: number;
savedData?: any;
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}