Skip to content

Commit 88c6494

Browse files
refactor(stateProvider): Moved $stateProvider out of core into ng1
Closes #3001
1 parent 980215a commit 88c6494

File tree

6 files changed

+77
-46
lines changed

6 files changed

+77
-46
lines changed

src/ng1/legacy/stateEvents.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ import {IServiceProviderFactory} from "angular";
2020
import {Obj} from "../../common/common";
2121
import {TargetState} from "../../state/targetState";
2222
import {StateService} from "../../state/stateService";
23-
import {StateProvider} from "../../state/state";
23+
import {StateProvider} from "../stateProvider";
2424
import {Transition} from "../../transition/transition";
2525
import IAngularEvent = angular.IAngularEvent;
2626
import {TransitionService} from "../../transition/transitionService";
2727
import {UrlRouter} from "../../url/urlRouter";
2828
import * as angular from 'angular';
2929
import IScope = angular.IScope;
3030
import {HookResult} from "../../transition/interface";
31+
import {UIInjector} from "../../common/interface";
3132

3233
/**
3334
* An event broadcast on `$rootScope` when the state transition **begins**.
@@ -164,7 +165,7 @@ export var $stateNotFound: IAngularEvent;
164165
if (!$transition$.options().notify || !$transition$.valid() || $transition$.ignored())
165166
return;
166167

167-
let $injector = $transition$.injector().native;
168+
let $injector = $transition$.injector();
168169
let $stateEvents = $injector.get('$stateEvents');
169170
let $rootScope = $injector.get('$rootScope');
170171
let $state = $injector.get('$state');
@@ -209,7 +210,11 @@ export var $stateNotFound: IAngularEvent;
209210
}
210211

211212
stateNotFoundHandler.$inject = ['$to$', '$from$', '$state', '$rootScope', '$urlRouter'];
212-
function stateNotFoundHandler($to$: TargetState, $from$: TargetState, $state: StateService, $rootScope: IScope, $urlRouter: UrlRouter): HookResult {
213+
function stateNotFoundHandler($to$: TargetState, $from$: TargetState, injector: UIInjector): HookResult {
214+
let $state: StateService = injector.get('$state');
215+
let $rootScope: IScope = injector.get('$rootScope');
216+
let $urlRouter: UrlRouter = injector.get('$urlRouter');
217+
213218
interface StateNotFoundEvent extends IAngularEvent {
214219
retry: Promise<any>;
215220
}
@@ -279,7 +284,7 @@ export var $stateNotFound: IAngularEvent;
279284
runtime = true;
280285

281286
if (enabledStateEvents["$stateNotFound"])
282-
$stateProvider.onInvalid(stateNotFoundHandler as Function);
287+
$stateProvider.onInvalid(stateNotFoundHandler);
283288
if (enabledStateEvents.$stateChangeStart)
284289
$transitions.onBefore({}, stateChangeStartHandler, {priority: 1000});
285290

src/ng1/services.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {TemplateFactory} from "./templateFactory";
2323
import {StateParams} from "../params/stateParams";
2424
import {TransitionService} from "../transition/transitionService";
2525
import {StateService} from "../state/stateService";
26-
import {StateProvider} from "../state/state";
26+
import {StateProvider} from "./stateProvider";
2727
import {UrlRouterProvider, UrlRouter} from "../url/urlRouter";
2828
import {UrlMatcherFactory} from "../url/urlMatcherFactory";
2929
import {getStateHookBuilder} from "./statebuilders/onEnterExitRetain";
@@ -158,13 +158,20 @@ export function annotateController(controllerExpression: (IInjectable|string)):
158158
}
159159

160160
let router: UIRouter = null;
161+
declare module "../router" {
162+
interface UIRouter {
163+
/** @hidden TODO: move this to ng1.ts */
164+
stateProvider: StateProvider;
165+
}
166+
}
161167

162168
$uiRouter.$inject = ['$locationProvider'];
163169
/** This angular 1 provider instantiates a Router and exposes its services via the angular injector */
164170
function $uiRouter($locationProvider: ILocationProvider) {
165171

166172
// Create a new instance of the Router when the $uiRouterProvider is initialized
167173
router = new UIRouter();
174+
router.stateProvider = new StateProvider(router.stateRegistry, router.stateService);
168175

169176
// Apply ng1 specific StateBuilder code for `views`, `resolve`, and `onExit/Retain/Enter` properties
170177
router.stateRegistry.decorator("views", ng1ViewsBuilder);

src/state/state.ts renamed to src/ng1/stateProvider.ts

+11-25
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
/** @module state */ /** for typedoc */
1+
/** @module ng1 */ /** for typedoc */
22
import {isObject} from "../common/predicates";
33
import {bindFunctions} from "../common/common";
4-
import {BuilderFunction} from "./stateBuilder";
5-
import {StateRegistry} from "./stateRegistry";
6-
import {StateDeclaration} from "./interface";
7-
import {State} from "./stateObject"; // has or is using
4+
import {BuilderFunction} from "../state/stateBuilder";
5+
import {StateRegistry} from "../state/stateRegistry";
6+
import {Ng1StateDeclaration} from "./interface";
7+
import {StateService, OnInvalidCallback} from "../state/stateService";
88

99
/**
1010
* @ngdoc object
@@ -28,8 +28,7 @@ import {State} from "./stateObject"; // has or is using
2828
* The `$stateProvider` provides interfaces to declare these states for your app.
2929
*/
3030
export class StateProvider {
31-
invalidCallbacks: Function[] = [];
32-
constructor(private stateRegistry: StateRegistry) {
31+
constructor(private stateRegistry: StateRegistry, private stateService: StateService) {
3332
bindFunctions(StateProvider.prototype, this, this);
3433
}
3534

@@ -262,8 +261,8 @@ export class StateProvider {
262261
* To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
263262
* @param {object} definition State configuration object.
264263
*/
265-
state(name: string, definition: StateDeclaration): StateProvider;
266-
state(definition: StateDeclaration): StateProvider;
264+
state(name: string, definition: Ng1StateDeclaration): StateProvider;
265+
state(definition: Ng1StateDeclaration): StateProvider;
267266
state(name: any, definition?: any) {
268267
if (isObject(name)) {
269268
definition = name;
@@ -277,23 +276,10 @@ export class StateProvider {
277276
/**
278277
* Registers an invalid state handler
279278
*
280-
* Registers a function to be injected and invoked when [[StateService.transitionTo]] has been called with an invalid
281-
* state reference parameter
282-
*
283-
* This function can be injected with one some special values:
284-
* - **`$to$`**: TargetState
285-
* - **`$from$`**: TargetState
286-
*
287-
* Note: This API is subject to change.
288-
* Replacement of dependency injection support with some alternative is likely.
289-
*
290-
* @param {function} callback
291-
* The function which will be injected and invoked, when a matching transition is started.
292-
* The function may optionally return a {TargetState} or a Promise for a TargetState. If one
293-
* is returned, it is treated as a redirect.
279+
* This is a passthrough to [[StateService.onInvalid]] for ng1.
294280
*/
295281

296-
onInvalid(callback: Function) {
297-
this.invalidCallbacks.push(callback);
282+
onInvalid(callback: OnInvalidCallback): Function {
283+
return this.stateService.onInvalid(callback);
298284
}
299285
}

src/router.ts

-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/** @module core */ /** */
22
import {UrlMatcherFactory} from "./url/urlMatcherFactory";
33
import {UrlRouterProvider} from "./url/urlRouter";
4-
import {StateProvider} from "./state/state";
54
import {UrlRouter} from "./url/urlRouter";
65
import {TransitionService} from "./transition/transitionService";
76
import {ViewService} from "./view/view";
@@ -33,9 +32,6 @@ export class UIRouter {
3332

3433
stateRegistry: StateRegistry = new StateRegistry(this.urlMatcherFactory, this.urlRouterProvider);
3534

36-
/** @hidden TODO: move this to ng1.ts */
37-
stateProvider = new StateProvider(this.stateRegistry);
38-
3935
stateService = new StateService(this);
4036

4137
constructor() {

src/state/module.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/** @module state */ /** for typedoc */
22
export * from "./interface";
3-
export * from "./state";
43
export * from "./stateBuilder";
54
export * from "./stateObject";
65
export * from "./stateMatcher";

src/state/stateService.ts

+49-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** @module state */ /** */
2-
import {extend, defaults, silentRejection, silenceUncaughtInPromise} from "../common/common";
2+
import {extend, defaults, silentRejection, silenceUncaughtInPromise, removeFrom} from "../common/common";
33
import {isDefined, isObject, isString} from "../common/predicates";
44
import {Queue} from "../common/queue";
55
import {services} from "../common/coreservices";
@@ -25,9 +25,16 @@ import {HrefOptions} from "./interface";
2525
import {bindFunctions} from "../common/common";
2626
import {Globals} from "../globals";
2727
import {UIRouter} from "../router";
28-
import {StateParams} from "../params/stateParams"; // for params() return type
28+
import {UIInjector} from "../common/interface";
29+
import {ResolveContext} from "../resolve/resolveContext";
30+
import {StateParams} from "../params/stateParams"; // has or is using
31+
32+
export type OnInvalidCallback =
33+
(toState?: TargetState, fromState?: TargetState, injector?: UIInjector) => HookResult;
2934

3035
export class StateService {
36+
invalidCallbacks: OnInvalidCallback[] = [];
37+
3138
get transition() { return this.router.globals.transition; }
3239
get params() { return this.router.globals.params; }
3340
get current() { return this.router.globals.current; }
@@ -49,16 +56,13 @@ export class StateService {
4956
*
5057
* If a callback returns an TargetState, then it is used as arguments to $state.transitionTo() and the result returned.
5158
*/
52-
private _handleInvalidTargetState(fromPath: PathNode[], $to$: TargetState) {
59+
private _handleInvalidTargetState(fromPath: PathNode[], toState: TargetState) {
60+
let fromState = PathFactory.makeTargetState(fromPath);
5361
let globals = <Globals> this.router.globals;
5462
const latestThing = () => globals.transitionHistory.peekTail();
5563
let latest = latestThing();
56-
let $from$ = PathFactory.makeTargetState(fromPath);
57-
let callbackQueue = new Queue<Function>(this.router.stateProvider.invalidCallbacks.slice());
58-
let {$q, $injector} = services;
59-
60-
const invokeCallback = (callback: Function) =>
61-
$q.when($injector.invoke(callback, null, { $to$, $from$ }));
64+
let callbackQueue = new Queue<OnInvalidCallback>(this.invalidCallbacks.slice());
65+
let injector = new ResolveContext(fromPath).injector();
6266

6367
const checkForRedirect = (result: HookResult) => {
6468
if (!(result instanceof TargetState)) {
@@ -76,13 +80,47 @@ export class StateService {
7680

7781
function invokeNextCallback() {
7882
let nextCallback = callbackQueue.dequeue();
79-
if (nextCallback === undefined) return Rejection.invalid($to$.error()).toPromise();
80-
return invokeCallback(nextCallback).then(checkForRedirect).then(result => result || invokeNextCallback());
83+
if (nextCallback === undefined) return Rejection.invalid(toState.error()).toPromise();
84+
85+
let callbackResult = services.$q.when(nextCallback(toState, fromState, injector));
86+
return callbackResult.then(checkForRedirect).then(result => result || invokeNextCallback());
8187
}
8288

8389
return invokeNextCallback();
8490
}
8591

92+
/**
93+
* Registers an Invalid State handler
94+
*
95+
* Registers a [[OnInvalidCallback]] function to be invoked when [[StateService.transitionTo]]
96+
* has been called with an invalid state reference parameter
97+
*
98+
* Example:
99+
* ```js
100+
* stateService.onInvalid(function(to, from, injector) {
101+
* if (to.name() === 'foo') {
102+
* let lazyLoader = injector.get('LazyLoadService');
103+
* return lazyLoader.load('foo')
104+
* .then(() => stateService.target('foo'));
105+
* }
106+
* });
107+
* ```
108+
*
109+
* @param {function} callback invoked when the toState is invalid
110+
* This function receives the (invalid) toState, the fromState, and an injector.
111+
* The function may optionally return a [[TargetState]] or a Promise for a TargetState.
112+
* If one is returned, it is treated as a redirect.
113+
*
114+
* @returns a function which deregisters the callback
115+
*/
116+
onInvalid(callback: OnInvalidCallback): Function {
117+
this.invalidCallbacks.push(callback);
118+
return function deregisterListener() {
119+
removeFrom(this.invalidCallbacks)(callback);
120+
}.bind(this);
121+
}
122+
123+
86124
/**
87125
* @ngdoc function
88126
* @name ui.router.state.$state#reload

0 commit comments

Comments
 (0)