Skip to content

Commit 0690917

Browse files
feat(*): Create router.dispose() to dispose a router instance and resources.
1 parent 31ac53c commit 0690917

13 files changed

+130
-37
lines changed

src/common/common.ts

+4
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ export const removeFrom = curry((array: any[], obj: any) => {
136136
return array;
137137
});
138138

139+
/** pushes a values to an array and returns the value */
140+
export const pushTo = <T> (arr: T[], val: T) =>
141+
(arr.push(val), val);
142+
139143
/**
140144
* Applies a set of defaults to an options object. The options object is filtered
141145
* to only those properties of the objects in the defaultsList.

src/common/trace.ts

+13-13
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,12 @@ export class Trace {
144144
}
145145

146146
/** @internalapi called by ui-router code */
147-
traceTransitionStart(transition: Transition) {
147+
traceTransitionStart(trans: Transition) {
148148
if (!this.enabled(Category.TRANSITION)) return;
149-
let tid = transition.$id,
149+
let tid = trans.$id,
150150
digest = this.approximateDigests,
151-
transitionStr = stringify(transition);
152-
console.log(`Transition #${tid} Digest #${digest}: Started -> ${transitionStr}`);
151+
transitionStr = stringify(trans);
152+
console.log(`Transition #${tid} r${trans.router.$id}: Started -> ${transitionStr}`);
153153
}
154154

155155
/** @internalapi called by ui-router code */
@@ -158,27 +158,27 @@ export class Trace {
158158
let tid = trans && trans.$id,
159159
digest = this.approximateDigests,
160160
transitionStr = stringify(trans);
161-
console.log(`Transition #${tid} Digest #${digest}: Ignored <> ${transitionStr}`);
161+
console.log(`Transition #${tid} r${trans.router.$id}: Ignored <> ${transitionStr}`);
162162
}
163163

164164
/** @internalapi called by ui-router code */
165-
traceHookInvocation(step: TransitionHook, options: any) {
165+
traceHookInvocation(step: TransitionHook, trans: Transition, options: any) {
166166
if (!this.enabled(Category.HOOK)) return;
167167
let tid = parse("transition.$id")(options),
168168
digest = this.approximateDigests,
169169
event = parse("traceData.hookType")(options) || "internal",
170170
context = parse("traceData.context.state.name")(options) || parse("traceData.context")(options) || "unknown",
171171
name = functionToString((step as any).registeredHook.callback);
172-
console.log(`Transition #${tid} Digest #${digest}: Hook -> ${event} context: ${context}, ${maxLength(200, name)}`);
172+
console.log(`Transition #${tid} r${trans.router.$id}: Hook -> ${event} context: ${context}, ${maxLength(200, name)}`);
173173
}
174174

175175
/** @internalapi called by ui-router code */
176-
traceHookResult(hookResult: HookResult, transitionOptions: any) {
176+
traceHookResult(hookResult: HookResult, trans: Transition, transitionOptions: any) {
177177
if (!this.enabled(Category.HOOK)) return;
178178
let tid = parse("transition.$id")(transitionOptions),
179179
digest = this.approximateDigests,
180180
hookResultStr = stringify(hookResult);
181-
console.log(`Transition #${tid} Digest #${digest}: <- Hook returned: ${maxLength(200, hookResultStr)}`);
181+
console.log(`Transition #${tid} r${trans.router.$id}: <- Hook returned: ${maxLength(200, hookResultStr)}`);
182182
}
183183

184184
/** @internalapi called by ui-router code */
@@ -187,7 +187,7 @@ export class Trace {
187187
let tid = trans && trans.$id,
188188
digest = this.approximateDigests,
189189
pathStr = path && path.toString();
190-
console.log(`Transition #${tid} Digest #${digest}: Resolving ${pathStr} (${when})`);
190+
console.log(`Transition #${tid} r${trans.router.$id}: Resolving ${pathStr} (${when})`);
191191
}
192192

193193
/** @internalapi called by ui-router code */
@@ -197,7 +197,7 @@ export class Trace {
197197
digest = this.approximateDigests,
198198
resolvableStr = resolvable && resolvable.toString(),
199199
result = stringify(resolvable.data);
200-
console.log(`Transition #${tid} Digest #${digest}: <- Resolved ${resolvableStr} to: ${maxLength(200, result)}`);
200+
console.log(`Transition #${tid} r${trans.router.$id}: <- Resolved ${resolvableStr} to: ${maxLength(200, result)}`);
201201
}
202202

203203
/** @internalapi called by ui-router code */
@@ -206,7 +206,7 @@ export class Trace {
206206
let tid = trans && trans.$id,
207207
digest = this.approximateDigests,
208208
transitionStr = stringify(trans);
209-
console.log(`Transition #${tid} Digest #${digest}: <- Rejected ${transitionStr}, reason: ${reason}`);
209+
console.log(`Transition #${tid} r${trans.router.$id}: <- Rejected ${transitionStr}, reason: ${reason}`);
210210
}
211211

212212
/** @internalapi called by ui-router code */
@@ -216,7 +216,7 @@ export class Trace {
216216
digest = this.approximateDigests,
217217
state = finalState.name,
218218
transitionStr = stringify(trans);
219-
console.log(`Transition #${tid} Digest #${digest}: <- Success ${transitionStr}, final state: ${state}`);
219+
console.log(`Transition #${tid} r${trans.router.$id}: <- Success ${transitionStr}, final state: ${state}`);
220220
}
221221

222222
/** @internalapi called by ui-router code */

src/interface.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,15 @@ export interface UIInjector {
8383
getNative<T>(token: any): T;
8484
}
8585

86-
export interface UIRouterPlugin {
86+
export interface UIRouterPlugin extends Disposable {
8787
name: string;
8888
}
8989

9090
export abstract class UIRouterPluginBase implements UIRouterPlugin {
9191
abstract name: string;
92+
dispose(router: UIRouter) { }
93+
}
94+
95+
export interface Disposable {
96+
dispose(router?: UIRouter);
9297
}

src/params/paramTypes.ts

+5
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ export class ParamTypes {
8686
this.types = inherit(map(this.defaultTypes, makeType), {});
8787
}
8888

89+
/** @internalapi */
90+
dispose() {
91+
this.types = {};
92+
}
93+
8994
type(name: string, definition?: ParamTypeDefinition, definitionFn?: () => ParamTypeDefinition) {
9095
if (!isDefined(definition)) return this.types[name];
9196
if (this.types.hasOwnProperty(name)) throw new Error(`A type named '${name}' has already been defined.`);

src/router.ts

+31-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import { ViewService } from "./view/view";
77
import { StateRegistry } from "./state/stateRegistry";
88
import { StateService } from "./state/stateService";
99
import { UIRouterGlobals, Globals } from "./globals";
10-
import { UIRouterPlugin } from "./interface";
11-
import { values } from "./common/common";
10+
import { UIRouterPlugin, Disposable } from "./interface";
11+
import { values, removeFrom } from "./common/common";
12+
13+
/** @hidden */
14+
let _routerInstance = 0;
1215

1316
/**
1417
* The master class used to instantiate an instance of UI-Router.
@@ -22,6 +25,9 @@ import { values } from "./common/common";
2225
* Tell UI-Router to monitor the URL by calling `uiRouter.urlRouter.listen()` ([[UrlRouter.listen]])
2326
*/
2427
export class UIRouter {
28+
/** @hidden */
29+
$id: number = _routerInstance++;
30+
2531
viewService = new ViewService();
2632

2733
transitionService: TransitionService = new TransitionService(this);
@@ -38,10 +44,32 @@ export class UIRouter {
3844

3945
stateService = new StateService(this);
4046

47+
private _disposables: Disposable[] = [];
48+
49+
/** Registers an object to be notified when the router is disposed */
50+
disposable(disposable: Disposable) {
51+
this._disposables.push(disposable);
52+
}
53+
54+
dispose() {
55+
this._disposables.slice().forEach(d => {
56+
try {
57+
typeof d.dispose === 'function' && d.dispose(this);
58+
removeFrom(this._disposables, d);
59+
} catch (ignored) {}
60+
});
61+
}
62+
4163
constructor() {
4264
this.viewService.rootContext(this.stateRegistry.root());
4365
this.globals.$current = this.stateRegistry.root();
4466
this.globals.current = this.globals.$current.self;
67+
68+
this.disposable(this.transitionService);
69+
this.disposable(this.urlRouterProvider);
70+
this.disposable(this.urlRouter);
71+
this.disposable(this.stateRegistry);
72+
4573
}
4674

4775
private _plugins: { [key: string]: UIRouterPlugin } = {};
@@ -108,6 +136,7 @@ export class UIRouter {
108136
plugin<T extends UIRouterPlugin>(plugin: any, options: any = {}): T {
109137
let pluginInstance = new plugin(this, options);
110138
if (!pluginInstance.name) throw new Error("Required property `name` missing on plugin: " + pluginInstance);
139+
this._disposables.push(pluginInstance);
111140
return this._plugins[pluginInstance.name] = pluginInstance;
112141
}
113142

src/state/stateQueueManager.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import {UrlRouterProvider} from "../url/urlRouter";
99
import {RawParams} from "../params/interface";
1010
import {StateRegistry, StateRegistryListener} from "./stateRegistry";
1111
import { Param } from "../params/param";
12+
import { Disposable } from "../interface";
1213

13-
export class StateQueueManager {
14+
export class StateQueueManager implements Disposable {
1415
queue: State[];
1516
private $state: StateService;
1617

@@ -23,6 +24,11 @@ export class StateQueueManager {
2324
this.queue = [];
2425
}
2526

27+
/** @internalapi */
28+
dispose() {
29+
this.queue = [];
30+
}
31+
2632
register(config: StateDeclaration) {
2733
let {states, queue, $state} = this;
2834
// Wrap a new object around the state so we can store our private details easily.

src/state/stateRegistry.ts

+17-10
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
/** @coreapi @module state */ /** for typedoc */
22

3-
import {State} from "./stateObject";
4-
import {StateMatcher} from "./stateMatcher";
5-
import {StateBuilder} from "./stateBuilder";
6-
import {StateQueueManager} from "./stateQueueManager";
7-
import {UrlMatcherFactory} from "../url/urlMatcherFactory";
8-
import {StateDeclaration} from "./interface";
9-
import {BuilderFunction} from "./stateBuilder";
10-
import {StateOrName} from "./interface";
11-
import {UrlRouterProvider} from "../url/urlRouter";
12-
import {removeFrom} from "../common/common";
3+
import { State } from "./stateObject";
4+
import { StateMatcher } from "./stateMatcher";
5+
import { StateBuilder } from "./stateBuilder";
6+
import { StateQueueManager } from "./stateQueueManager";
7+
import { UrlMatcherFactory } from "../url/urlMatcherFactory";
8+
import { StateDeclaration } from "./interface";
9+
import { BuilderFunction } from "./stateBuilder";
10+
import { StateOrName } from "./interface";
11+
import { UrlRouterProvider } from "../url/urlRouter";
12+
import { removeFrom } from "../common/common";
1313

1414
/**
1515
* The signature for the callback function provided to [[StateRegistry.onStateRegistryEvent]].
@@ -50,6 +50,13 @@ export class StateRegistry {
5050
_root.navigable = null;
5151
}
5252

53+
/** @internalapi */
54+
dispose() {
55+
this.stateQueue.dispose();
56+
this.listeners = [];
57+
this.get().forEach(state => this.get(state) && this.deregister(state));
58+
}
59+
5360
/**
5461
* Listen for a State Registry events
5562
*

src/state/stateService.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** @coreapi @module state */ /** */
2-
import {extend, defaults, silentRejection, silenceUncaughtInPromise, removeFrom} from "../common/common";
2+
import { extend, defaults, silentRejection, silenceUncaughtInPromise, removeFrom, noop } from "../common/common";
33
import {isDefined, isObject, isString} from "../common/predicates";
44
import {Queue} from "../common/queue";
55
import {services} from "../common/coreservices";
@@ -74,6 +74,12 @@ export class StateService {
7474
bindFunctions(StateService.prototype, this, this, boundFns);
7575
}
7676

77+
/** @internalapi */
78+
dispose() {
79+
this.defaultErrorHandler(noop);
80+
this.invalidCallbacks = [];
81+
}
82+
7783
/**
7884
* Handler for when [[transitionTo]] is called with an invalid state.
7985
*

src/transition/transition.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ import {Globals} from "../globals";
3434
import {UIInjector} from "../interface";
3535
import {RawParams} from "../params/interface";
3636

37-
/** @hidden */
38-
let transitionCount = 0;
3937
/** @hidden */
4038
const stateSelf: (_state: State) => StateDeclaration = prop("self");
4139

@@ -150,7 +148,7 @@ export class Transition implements IHookRegistry {
150148

151149
// current() is assumed to come from targetState.options, but provide a naive implementation otherwise.
152150
this._options = extend({ current: val(this) }, targetState.options());
153-
this.$id = transitionCount++;
151+
this.$id = router.transitionService._transitionCount++;
154152
let toPath = PathFactory.buildToPath(fromPath, targetState);
155153
this._treeChanges = PathFactory.treeChanges(fromPath, toPath, this._options.reloadState);
156154
this.createTransitionHookRegFns();

src/transition/transitionHook.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export class TransitionHook {
6565
if (hook._deregistered) return;
6666

6767
let options = this.options;
68-
trace.traceHookInvocation(this, options);
68+
trace.traceHookInvocation(this, this.transition, options);
6969

7070
if (this.rejectIfSuperseded()) {
7171
return Rejection.superseded(options.current()).toPromise();
@@ -114,7 +114,7 @@ export class TransitionHook {
114114
return result.then(this.handleHookResult.bind(this));
115115
}
116116

117-
trace.traceHookResult(result, this.options);
117+
trace.traceHookResult(result, this.transition, this.options);
118118

119119
// Hook returned false
120120
if (result === false) {

src/transition/transitionService.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {registerLazyLoadHook} from "../hooks/lazyLoad";
2424
import {TransitionHookType} from "./transitionHookType";
2525
import {TransitionHook} from "./transitionHook";
2626
import {isDefined} from "../common/predicates";
27+
import { removeFrom, values } from "../common/common";
28+
import { Disposable } from "../interface";
2729

2830
/**
2931
* The default [[Transition]] options.
@@ -52,7 +54,9 @@ export let defaultTransOpts: TransitionOptions = {
5254
*
5355
* At bootstrap, [[UIRouter]] creates a single instance (singleton) of this class.
5456
*/
55-
export class TransitionService implements IHookRegistry {
57+
export class TransitionService implements IHookRegistry, Disposable {
58+
/** @hidden */
59+
_transitionCount = 0;
5660

5761
/**
5862
* Registers a [[TransitionHookFn]], called *while a transition is being constructed*.
@@ -128,6 +132,16 @@ export class TransitionService implements IHookRegistry {
128132
this.registerTransitionHooks();
129133
}
130134

135+
/** @internalapi */
136+
dispose() {
137+
delete this._router.globals.transition;
138+
139+
values(this._transitionHooks).forEach(hooksArray => hooksArray.forEach(hook => {
140+
hook._deregistered = true;
141+
removeFrom(hooksArray, hook);
142+
}));
143+
}
144+
131145
/**
132146
* Creates a new [[Transition]] object
133147
*

src/url/urlMatcherFactory.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {matcherConfig} from "./urlMatcherConfig";
77
import {Param} from "../params/param";
88
import {ParamTypes} from "../params/paramTypes";
99
import {ParamTypeDefinition} from "../params/interface";
10+
import { Disposable } from "../interface";
1011

1112
/** @hidden */
1213
function getDefaultConfig() {
@@ -22,7 +23,7 @@ function getDefaultConfig() {
2223
* The factory is available to ng1 services as
2324
* `$urlMatcherFactor` or ng1 providers as `$urlMatcherFactoryProvider`.
2425
*/
25-
export class UrlMatcherFactory {
26+
export class UrlMatcherFactory implements Disposable {
2627
paramTypes = new ParamTypes();
2728

2829
constructor() {
@@ -123,4 +124,9 @@ export class UrlMatcherFactory {
123124
this.paramTypes._flushTypeQueue();
124125
return this;
125126
};
127+
128+
/** @internalapi */
129+
dispose() {
130+
this.paramTypes.dispose();
131+
}
126132
}

0 commit comments

Comments
 (0)