Skip to content

Commit b28938e

Browse files
ADjenkovADjenkov
ADjenkov
authored and
ADjenkov
committed
fix-next(dialogs): handle close modal
Sanitize router states and cache when closing modal view
1 parent d1af3ce commit b28938e

File tree

4 files changed

+104
-32
lines changed

4 files changed

+104
-32
lines changed

Diff for: nativescript-angular/directives/dialogs.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ViewContainerRef,
1010
} from "@angular/core";
1111

12+
import { NSLocationStrategy } from "../router/ns-location-strategy";
1213
import { Page } from "tns-core-modules/ui/page";
1314
import { View, ViewBase } from "tns-core-modules/ui/core/view";
1415
import { ProxyViewContainer } from "tns-core-modules/ui/proxy-view-container/proxy-view-container";
@@ -44,6 +45,9 @@ interface ShowDialogOptions {
4445

4546
@Injectable()
4647
export class ModalDialogService {
48+
constructor(private location: NSLocationStrategy) {
49+
}
50+
4751
public showModal(type: Type<any>,
4852
{ viewContainerRef, moduleRef, context, fullscreen }: ModalDialogOptions
4953
): Promise<any> {
@@ -66,8 +70,10 @@ export class ModalDialogService {
6670
const componentContainer = moduleRef || viewContainerRef;
6771
const resolver = componentContainer.injector.get(ComponentFactoryResolver);
6872

73+
this.location._beginModalNavigation();
74+
6975
return new Promise(resolve => {
70-
setTimeout(() => ModalDialogService.showDialog({
76+
setTimeout(() => this._showDialog({
7177
containerRef: viewContainerRef,
7278
context,
7379
doneCallback: resolve,
@@ -80,7 +86,7 @@ export class ModalDialogService {
8086
});
8187
}
8288

83-
private static showDialog({
89+
private _showDialog({
8490
containerRef,
8591
context,
8692
doneCallback,
@@ -98,6 +104,10 @@ export class ModalDialogService {
98104
if (componentView) {
99105
componentView.closeModal();
100106
}
107+
108+
this.location._beginCloseModalNavigation();
109+
this.location.back();
110+
this.location._finishCloseModalNavigation();
101111
detachedLoaderRef.instance.detectChanges();
102112
detachedLoaderRef.destroy();
103113
};

Diff for: nativescript-angular/router/ns-location-strategy.ts

+55-5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface LocationState {
2424
queryParams: string;
2525
segmentGroup: UrlSegmentGroup;
2626
isPageNavigation: boolean;
27+
isModalNavigation: boolean;
2728
}
2829

2930
@Injectable()
@@ -33,9 +34,13 @@ export class NSLocationStrategy extends LocationStrategy {
3334
private currentOutlet: string;
3435
private popStateCallbacks = new Array<(_: any) => any>();
3536

37+
private _isModalClosing = false;
3638
private _isPageNavigationBack = false;
3739
private _currentNavigationOptions: NavigationOptions;
3840

41+
42+
public _isModalNavigation = false;
43+
3944
constructor(private frameService: FrameService) {
4045
super();
4146
routerLog("NSLocationStrategy.constructor()");
@@ -87,7 +92,8 @@ export class NSLocationStrategy extends LocationStrategy {
8792
url: url,
8893
queryParams: queryParams,
8994
segmentGroup: rootOutlets[outletName],
90-
isPageNavigation: isNewPage
95+
isPageNavigation: isNewPage,
96+
isModalNavigation: false
9197
});
9298
}
9399
});
@@ -130,7 +136,22 @@ export class NSLocationStrategy extends LocationStrategy {
130136
}
131137

132138
back(): void {
133-
if (this._isPageNavigationBack) {
139+
if (this._isModalClosing) {
140+
const states = this.statesByOutlet[this.currentOutlet];
141+
// We are closing modal view
142+
// clear the stack until we get to the page that opened the modal view
143+
let state = states.pop();
144+
let count = 1;
145+
146+
while (!(state.isModalNavigation)) {
147+
state = states.pop();
148+
count++;
149+
}
150+
151+
states.push(state);
152+
routerLog("NSLocationStrategy.back() while closing modal. States popped: " + count);
153+
this.callPopState(state, true);
154+
} else if (this._isPageNavigationBack) {
134155
const states = this.statesByOutlet[this.currentOutlet];
135156
// We are navigating to the previous page
136157
// clear the stack until we get to a page navigation state
@@ -178,6 +199,7 @@ export class NSLocationStrategy extends LocationStrategy {
178199

179200
private callPopState(state: LocationState, pop: boolean = true) {
180201
const urlSerializer = new DefaultUrlSerializer();
202+
this.currentUrlTree.root.children[this.currentOutlet] = state.segmentGroup;
181203
const url = urlSerializer.serialize(this.currentUrlTree);
182204
const change = { url: url, pop: pop };
183205
for (let fn of this.popStateCallbacks) {
@@ -199,11 +221,12 @@ export class NSLocationStrategy extends LocationStrategy {
199221
Object.keys(this.statesByOutlet).forEach(outletName => {
200222
const outletStates = this.statesByOutlet[outletName];
201223
const outletLog = outletStates
202-
.map((v, i) => `${i}.[${v.isPageNavigation ? "PAGE" : "INTERNAL"}] "${v.segmentGroup.toString()}"`)
224+
// tslint:disable-next-line:max-line-length
225+
.map((v, i) => `${outletName}.${i}.[${v.isPageNavigation ? "PAGE" : "INTERNAL"}].[${v.isModalNavigation ? "MODAL" : "BASE"}] "${v.segmentGroup.toString()}"`)
203226
.reverse();
204227

205-
result.push(`${outletName} :`);
206-
result = result.concat(result, outletLog);
228+
229+
result = result.concat(outletLog);
207230
});
208231

209232
return result.join("\n");
@@ -231,6 +254,33 @@ export class NSLocationStrategy extends LocationStrategy {
231254
return this._isPageNavigationBack;
232255
}
233256

257+
public _beginModalNavigation(): void {
258+
routerLog("NSLocationStrategy._beginModalNavigation()");
259+
const lastState = this.peekState(this.currentOutlet);
260+
261+
if (lastState) {
262+
lastState.isModalNavigation = true;
263+
}
264+
265+
this._isModalNavigation = true;
266+
}
267+
268+
public _beginCloseModalNavigation(): void {
269+
routerLog("NSLocationStrategy.startCloseModal()");
270+
if (this._isModalClosing) {
271+
throw new Error("Calling startCloseModal while closing modal.");
272+
}
273+
this._isModalClosing = true;
274+
}
275+
276+
public _finishCloseModalNavigation() {
277+
routerLog("NSLocationStrategy.finishCloseModalNavigation()");
278+
if (!this._isModalClosing) {
279+
throw new Error("Calling startCloseModal while not closing modal.");
280+
}
281+
this._isModalClosing = false;
282+
}
283+
234284
public _beginPageNavigation(name: string): NavigationOptions {
235285
routerLog("NSLocationStrategy._beginPageNavigation()");
236286
const lastState = this.peekState(name);

Diff for: nativescript-angular/router/ns-route-reuse-strategy.ts

+29-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
interface CacheItem {
1313
key: string;
1414
state: DetachedRouteHandle;
15+
isModal: boolean;
1516
}
1617

1718
/**
@@ -48,6 +49,27 @@ class DetachedStateCache {
4849
destroyComponentRef(state.componentRef);
4950
}
5051
}
52+
53+
public clearModalCache() {
54+
let removedItemsCount = 0;
55+
this.cache = this.cache.filter(cacheItem => {
56+
57+
if (cacheItem.isModal) {
58+
const state = <any>cacheItem.state;
59+
60+
if (!state.componentRef) {
61+
throw new Error("No componentRed found in DetachedRouteHandle");
62+
}
63+
64+
destroyComponentRef(state.componentRef);
65+
removedItemsCount++;
66+
}
67+
68+
return !cacheItem.isModal;
69+
});
70+
71+
log(`DetachedStateCache.clearModalCache() ${removedItemsCount} items will be destroyed`);
72+
}
5173
}
5274

5375
/**
@@ -100,7 +122,7 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
100122
const cache = this.cacheByOutlet[route.outlet] = this.cacheByOutlet[route.outlet] || new DetachedStateCache();
101123

102124
if (state) {
103-
cache.push({ key, state });
125+
cache.push({ key, state, isModal: this.location._isModalNavigation });
104126
} else {
105127
const topItem = cache.peek();
106128
if (topItem.key === key) {
@@ -148,11 +170,15 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
148170
return shouldReuse;
149171
}
150172

151-
clearCache(outletName: string) {
173+
clearCache(outletName: string, modalItemsOnly?: boolean) {
152174
const cache = this.cacheByOutlet[outletName];
153175

154176
if (cache) {
155-
cache.clear();
177+
if (modalItemsOnly) {
178+
cache.clearModalCache();
179+
} else {
180+
cache.clear();
181+
}
156182
}
157183
}
158184
}

Diff for: nativescript-angular/router/page-router-outlet.ts

+8-22
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ function routeToString(activatedRoute: ActivatedRoute | ActivatedRouteSnapshot):
102102
}
103103

104104
@Directive({ selector: "page-router-outlet" }) // tslint:disable-line:directive-selector
105-
export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-line:directive-class-suffix
105+
export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:directive-class-suffix
106106
private activated: ComponentRef<any> | null = null;
107107
private _activatedRoute: ActivatedRoute | null = null;
108108
private detachedLoaderFactory: ComponentFactory<DetachedLoader>;
@@ -161,28 +161,14 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
161161
this.detachedLoaderFactory = resolver.resolveComponentFactory(DetachedLoader);
162162
}
163163

164-
ngOnInit(): void {
165-
if (this.isActivated) {
166-
return;
167-
}
168-
169-
// If the outlet was not instantiated at the time the route got activated we need to populate
170-
// the outlet when it is initialized (ie inside a NgIf)
171-
const context = this.parentContexts.getContext(this.name);
172-
if (!context || !context.route) {
173-
return;
174-
}
164+
ngOnDestroy(): void {
175165

176-
if (context.attachRef) {
177-
// `attachRef` is populated when there is an existing component to mount
178-
this.attach(context.attachRef, context.route);
179-
} else {
180-
// otherwise the component defined in the configuration is created
181-
this.activateWith(context.route, context.resolver || null);
166+
// Clear accumulated modal view page cache when page-router-outlet
167+
// destroyed on modal view closing
168+
if (this.locationStrategy._isModalNavigation) {
169+
this.routeReuseStrategy.clearCache(this.name, true);
170+
this.locationStrategy._isModalNavigation = false;
182171
}
183-
}
184-
185-
ngOnDestroy(): void {
186172
this.parentContexts.onChildOutletDestroyed(this.name);
187173
}
188174

@@ -267,7 +253,7 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
267253
loadedResolver: ComponentFactoryResolver
268254
): void {
269255
log("PageRouterOutlet.activate() forward navigation - " +
270-
"create detached loader in the loader container");
256+
"create detached loader in the loader container");
271257

272258
const factory = this.getComponentFactory(activatedRoute, loadedResolver);
273259
const page = this.pageFactory({

0 commit comments

Comments
 (0)