mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 03:00:58 +08:00
fix(angular): save internal data
fixes #14888 fixes #14885 fixes #15054 fixes #15050
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
import { Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Injector, Input, OnDestroy, OnInit, Optional, Output, ViewContainerRef } from '@angular/core';
|
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 { ActivatedRoute, ChildrenOutletContexts, OutletContext, PRIMARY_OUTLET, Router } from '@angular/router';
|
||||||
import { StackController } from './router-controller';
|
import { StackController, RouteView } from './router-controller';
|
||||||
import { NavController } from '../../providers/nav-controller';
|
import { NavController } from '../../providers/nav-controller';
|
||||||
import { bindLifecycleEvents } from '../../providers/angular-delegate';
|
import { bindLifecycleEvents } from '../../providers/angular-delegate';
|
||||||
|
|
||||||
@ -11,6 +11,7 @@ import { bindLifecycleEvents } from '../../providers/angular-delegate';
|
|||||||
export class IonRouterOutlet implements OnDestroy, OnInit {
|
export class IonRouterOutlet implements OnDestroy, OnInit {
|
||||||
|
|
||||||
private activated: ComponentRef<any> | null = null;
|
private activated: ComponentRef<any> | null = null;
|
||||||
|
private activatedView: RouteView | null = null;
|
||||||
|
|
||||||
private _activatedRoute: ActivatedRoute | null = null;
|
private _activatedRoute: ActivatedRoute | null = null;
|
||||||
private name: string;
|
private name: string;
|
||||||
@ -42,25 +43,24 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
console.log('router-outlet destroyed');
|
||||||
this.parentContexts.onChildOutletDestroyed(this.name);
|
this.parentContexts.onChildOutletDestroyed(this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getContext(): OutletContext | null {
|
||||||
|
return this.parentContexts.getContext(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (!this.activated) {
|
if (!this.activated) {
|
||||||
// If the outlet was not instantiated at the time the route got activated we need to populate
|
// 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)
|
// 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 && 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
get isActivated(): boolean { return !!this.activated; }
|
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
|
* Called when the `RouteReuseStrategy` instructs to detach the subtree
|
||||||
*/
|
*/
|
||||||
detach(): ComponentRef<any> {
|
detach(): ComponentRef<any> {
|
||||||
if (!this.activated) {
|
throw new Error('incompatible reuse strategy');
|
||||||
throw new Error('Outlet is not activated');
|
|
||||||
}
|
|
||||||
this.location.detach();
|
|
||||||
const cmp = this.activated;
|
|
||||||
this.activated = null;
|
|
||||||
this._activatedRoute = null;
|
|
||||||
return cmp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree
|
* Called when the `RouteReuseStrategy` instructs to re-attach a previously detached subtree
|
||||||
*/
|
*/
|
||||||
attach(ref: ComponentRef<any>, activatedRoute: ActivatedRoute) {
|
attach(_ref: ComponentRef<any>, _activatedRoute: ActivatedRoute) {
|
||||||
this.activated = ref;
|
throw new Error('incompatible reuse strategy');
|
||||||
this._activatedRoute = activatedRoute;
|
|
||||||
this.location.insert(ref.hostView);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deactivate(): void {
|
deactivate(): void {
|
||||||
if (this.activated) {
|
if (this.activated) {
|
||||||
|
if (this.activatedView) {
|
||||||
|
this.activatedView.savedData = new Map(this.getContext()!.children['contexts']);
|
||||||
|
}
|
||||||
const c = this.component;
|
const c = this.component;
|
||||||
|
this.activatedView = null;
|
||||||
this.activated = null;
|
this.activated = null;
|
||||||
this._activatedRoute = null;
|
this._activatedRoute = null;
|
||||||
this.deactivateEvents.emit(c);
|
this.deactivateEvents.emit(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null) {
|
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null) {
|
||||||
if (this.isActivated) {
|
if (this.isActivated) {
|
||||||
throw new Error('Cannot activate an already activated outlet');
|
throw new Error('Cannot activate an already activated outlet');
|
||||||
}
|
}
|
||||||
this._activatedRoute = activatedRoute;
|
this._activatedRoute = activatedRoute;
|
||||||
|
|
||||||
|
let cmpRef: any;
|
||||||
let enteringView = this.stackCtrl.getExistingView(activatedRoute);
|
let enteringView = this.stackCtrl.getExistingView(activatedRoute);
|
||||||
if (enteringView) {
|
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 {
|
} else {
|
||||||
const snapshot = (activatedRoute as any)._futureSnapshot;
|
const snapshot = (activatedRoute as any)._futureSnapshot;
|
||||||
const component = snapshot.routeConfig!.component as any;
|
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 childContexts = this.parentContexts.getOrCreateContext(this.name).children;
|
||||||
|
|
||||||
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
|
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
|
// Calling `markForCheck` to make sure we will run the change detection when the
|
||||||
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
|
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
|
||||||
@ -146,10 +148,12 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { direction, animated } = this.navCtrl.consumeTransition();
|
const { direction, animated } = this.navCtrl.consumeTransition();
|
||||||
await this.stackCtrl.setActive(enteringView, direction, animated);
|
this.activatedView = enteringView;
|
||||||
this.activateEvents.emit(this.activated.instance);
|
this.stackCtrl.setActive(enteringView, direction, animated).then(() => {
|
||||||
|
this.activateEvents.emit(cmpRef.instance);
|
||||||
emitEvent(this.elementRef.nativeElement);
|
emitEvent(this.elementRef.nativeElement);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canGoBack(deep = 1) {
|
canGoBack(deep = 1) {
|
||||||
|
@ -80,9 +80,11 @@ export class StackController {
|
|||||||
.forEach(view => destroyView(view));
|
.forEach(view => destroyView(view));
|
||||||
|
|
||||||
for (let i = 0; i < views.length - 1; i++) {
|
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.setAttribute('aria-hidden', 'true');
|
||||||
element.classList.add('ion-page-hidden');
|
element.classList.add('ion-page-hidden');
|
||||||
|
// view.ref.changeDetectorRef.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.viewsSnapshot = views.slice();
|
this.viewsSnapshot = views.slice();
|
||||||
@ -149,4 +151,5 @@ export interface RouteView {
|
|||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
ref: ComponentRef<any>;
|
ref: ComponentRef<any>;
|
||||||
deactivatedId: number;
|
deactivatedId: number;
|
||||||
|
savedData?: any;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
|
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router';
|
||||||
import { deepEqual, objectValues } from './util';
|
|
||||||
|
|
||||||
export class IonicRouteStrategy implements RouteReuseStrategy {
|
export class IonicRouteStrategy implements RouteReuseStrategy {
|
||||||
|
|
||||||
shouldDetach(_route: ActivatedRouteSnapshot): boolean {
|
shouldDetach(_route: ActivatedRouteSnapshot): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line
|
|
||||||
store(_route: ActivatedRouteSnapshot, _detachedTree: DetachedRouteHandle): void {}
|
|
||||||
|
|
||||||
shouldAttach(_route: ActivatedRouteSnapshot): boolean {
|
shouldAttach(_route: ActivatedRouteSnapshot): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
store(_route: ActivatedRouteSnapshot, _detachedTree: DetachedRouteHandle): void {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
retrieve(_route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
|
retrieve(_route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -21,23 +22,29 @@ export class IonicRouteStrategy implements RouteReuseStrategy {
|
|||||||
future: ActivatedRouteSnapshot,
|
future: ActivatedRouteSnapshot,
|
||||||
curr: ActivatedRouteSnapshot
|
curr: ActivatedRouteSnapshot
|
||||||
): boolean {
|
): boolean {
|
||||||
// checking router params
|
if (future.routeConfig !== curr.routeConfig) {
|
||||||
const futureParams = objectValues(future.params);
|
return false;
|
||||||
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.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,10 +30,6 @@ export function ensureElementInBody(elementName: string) {
|
|||||||
return element as HTMLStencilElement;
|
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) {
|
export function deepEqual(x: any, y: any) {
|
||||||
if (x === y) {
|
if (x === y) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -62,6 +62,9 @@ export function matches(view: ViewController | undefined, id: string, params: Co
|
|||||||
const currentParams = view.params;
|
const currentParams = view.params;
|
||||||
const null1 = (currentParams == null);
|
const null1 = (currentParams == null);
|
||||||
const null2 = (params == null);
|
const null2 = (params == null);
|
||||||
|
if (currentParams === params) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (null1 !== null2) {
|
if (null1 !== null2) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user