Skip to content

Commit

Permalink
fix(angular): save internal data
Browse files Browse the repository at this point in the history
fixes #14888
fixes #14885
fixes #15054
fixes #15050
  • Loading branch information
manucorporat committed Aug 16, 2018
1 parent fef751d commit f84bb76
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 54 deletions.
62 changes: 33 additions & 29 deletions angular/src/directives/navigation/ion-router-outlet.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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) {
Expand Down
5 changes: 4 additions & 1 deletion angular/src/directives/navigation/router-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -149,4 +151,5 @@ export interface RouteView {
element: HTMLElement;
ref: ComponentRef<any>;
deactivatedId: number;
savedData?: any;
}
47 changes: 27 additions & 20 deletions angular/src/util/ionic-router-reuse-strategy.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Expand All @@ -21,23 +22,29 @@ export class IonicRouteStrategy implements RouteReuseStrategy {
future: ActivatedRouteSnapshot,
curr: ActivatedRouteSnapshot
): boolean {
if (future.routeConfig !== curr.routeConfig) {
return false;
}
if (future.component !== curr.component) {
return false;
}

// 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;
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;
}
}
4 changes: 0 additions & 4 deletions angular/src/util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions core/src/components/nav/view-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

0 comments on commit f84bb76

Please sign in to comment.