Skip to content

Commit

Permalink
refactor(stateProvider): Moved $stateProvider out of core into ng1
Browse files Browse the repository at this point in the history
Closes #3001
  • Loading branch information
christopherthielen committed Sep 21, 2016
1 parent 980215a commit 88c6494
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 46 deletions.
13 changes: 9 additions & 4 deletions src/ng1/legacy/stateEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ import {IServiceProviderFactory} from "angular";
import {Obj} from "../../common/common";
import {TargetState} from "../../state/targetState";
import {StateService} from "../../state/stateService";
import {StateProvider} from "../../state/state";
import {StateProvider} from "../stateProvider";
import {Transition} from "../../transition/transition";
import IAngularEvent = angular.IAngularEvent;
import {TransitionService} from "../../transition/transitionService";
import {UrlRouter} from "../../url/urlRouter";
import * as angular from 'angular';
import IScope = angular.IScope;
import {HookResult} from "../../transition/interface";
import {UIInjector} from "../../common/interface";

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

let $injector = $transition$.injector().native;
let $injector = $transition$.injector();
let $stateEvents = $injector.get('$stateEvents');
let $rootScope = $injector.get('$rootScope');
let $state = $injector.get('$state');
Expand Down Expand Up @@ -209,7 +210,11 @@ export var $stateNotFound: IAngularEvent;
}

stateNotFoundHandler.$inject = ['$to$', '$from$', '$state', '$rootScope', '$urlRouter'];
function stateNotFoundHandler($to$: TargetState, $from$: TargetState, $state: StateService, $rootScope: IScope, $urlRouter: UrlRouter): HookResult {
function stateNotFoundHandler($to$: TargetState, $from$: TargetState, injector: UIInjector): HookResult {
let $state: StateService = injector.get('$state');
let $rootScope: IScope = injector.get('$rootScope');
let $urlRouter: UrlRouter = injector.get('$urlRouter');

interface StateNotFoundEvent extends IAngularEvent {
retry: Promise<any>;
}
Expand Down Expand Up @@ -279,7 +284,7 @@ export var $stateNotFound: IAngularEvent;
runtime = true;

if (enabledStateEvents["$stateNotFound"])
$stateProvider.onInvalid(stateNotFoundHandler as Function);
$stateProvider.onInvalid(stateNotFoundHandler);
if (enabledStateEvents.$stateChangeStart)
$transitions.onBefore({}, stateChangeStartHandler, {priority: 1000});

Expand Down
9 changes: 8 additions & 1 deletion src/ng1/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {TemplateFactory} from "./templateFactory";
import {StateParams} from "../params/stateParams";
import {TransitionService} from "../transition/transitionService";
import {StateService} from "../state/stateService";
import {StateProvider} from "../state/state";
import {StateProvider} from "./stateProvider";
import {UrlRouterProvider, UrlRouter} from "../url/urlRouter";
import {UrlMatcherFactory} from "../url/urlMatcherFactory";
import {getStateHookBuilder} from "./statebuilders/onEnterExitRetain";
Expand Down Expand Up @@ -158,13 +158,20 @@ export function annotateController(controllerExpression: (IInjectable|string)):
}

let router: UIRouter = null;
declare module "../router" {
interface UIRouter {
/** @hidden TODO: move this to ng1.ts */
stateProvider: StateProvider;
}
}

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

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

// Apply ng1 specific StateBuilder code for `views`, `resolve`, and `onExit/Retain/Enter` properties
router.stateRegistry.decorator("views", ng1ViewsBuilder);
Expand Down
36 changes: 11 additions & 25 deletions src/state/state.ts → src/ng1/stateProvider.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/** @module state */ /** for typedoc */
/** @module ng1 */ /** for typedoc */
import {isObject} from "../common/predicates";
import {bindFunctions} from "../common/common";
import {BuilderFunction} from "./stateBuilder";
import {StateRegistry} from "./stateRegistry";
import {StateDeclaration} from "./interface";
import {State} from "./stateObject"; // has or is using
import {BuilderFunction} from "../state/stateBuilder";
import {StateRegistry} from "../state/stateRegistry";
import {Ng1StateDeclaration} from "./interface";
import {StateService, OnInvalidCallback} from "../state/stateService";

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

Expand Down Expand Up @@ -262,8 +261,8 @@ export class StateProvider {
* To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
* @param {object} definition State configuration object.
*/
state(name: string, definition: StateDeclaration): StateProvider;
state(definition: StateDeclaration): StateProvider;
state(name: string, definition: Ng1StateDeclaration): StateProvider;
state(definition: Ng1StateDeclaration): StateProvider;
state(name: any, definition?: any) {
if (isObject(name)) {
definition = name;
Expand All @@ -277,23 +276,10 @@ export class StateProvider {
/**
* Registers an invalid state handler
*
* Registers a function to be injected and invoked when [[StateService.transitionTo]] has been called with an invalid
* state reference parameter
*
* This function can be injected with one some special values:
* - **`$to$`**: TargetState
* - **`$from$`**: TargetState
*
* Note: This API is subject to change.
* Replacement of dependency injection support with some alternative is likely.
*
* @param {function} callback
* The function which will be injected and invoked, when a matching transition is started.
* The function may optionally return a {TargetState} or a Promise for a TargetState. If one
* is returned, it is treated as a redirect.
* This is a passthrough to [[StateService.onInvalid]] for ng1.
*/

onInvalid(callback: Function) {
this.invalidCallbacks.push(callback);
onInvalid(callback: OnInvalidCallback): Function {
return this.stateService.onInvalid(callback);
}
}
4 changes: 0 additions & 4 deletions src/router.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/** @module core */ /** */
import {UrlMatcherFactory} from "./url/urlMatcherFactory";
import {UrlRouterProvider} from "./url/urlRouter";
import {StateProvider} from "./state/state";
import {UrlRouter} from "./url/urlRouter";
import {TransitionService} from "./transition/transitionService";
import {ViewService} from "./view/view";
Expand Down Expand Up @@ -33,9 +32,6 @@ export class UIRouter {

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

/** @hidden TODO: move this to ng1.ts */
stateProvider = new StateProvider(this.stateRegistry);

stateService = new StateService(this);

constructor() {
Expand Down
1 change: 0 additions & 1 deletion src/state/module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/** @module state */ /** for typedoc */
export * from "./interface";
export * from "./state";
export * from "./stateBuilder";
export * from "./stateObject";
export * from "./stateMatcher";
Expand Down
60 changes: 49 additions & 11 deletions src/state/stateService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** @module state */ /** */
import {extend, defaults, silentRejection, silenceUncaughtInPromise} from "../common/common";
import {extend, defaults, silentRejection, silenceUncaughtInPromise, removeFrom} from "../common/common";
import {isDefined, isObject, isString} from "../common/predicates";
import {Queue} from "../common/queue";
import {services} from "../common/coreservices";
Expand All @@ -25,9 +25,16 @@ import {HrefOptions} from "./interface";
import {bindFunctions} from "../common/common";
import {Globals} from "../globals";
import {UIRouter} from "../router";
import {StateParams} from "../params/stateParams"; // for params() return type
import {UIInjector} from "../common/interface";
import {ResolveContext} from "../resolve/resolveContext";
import {StateParams} from "../params/stateParams"; // has or is using

export type OnInvalidCallback =
(toState?: TargetState, fromState?: TargetState, injector?: UIInjector) => HookResult;

export class StateService {
invalidCallbacks: OnInvalidCallback[] = [];

get transition() { return this.router.globals.transition; }
get params() { return this.router.globals.params; }
get current() { return this.router.globals.current; }
Expand All @@ -49,16 +56,13 @@ export class StateService {
*
* If a callback returns an TargetState, then it is used as arguments to $state.transitionTo() and the result returned.
*/
private _handleInvalidTargetState(fromPath: PathNode[], $to$: TargetState) {
private _handleInvalidTargetState(fromPath: PathNode[], toState: TargetState) {
let fromState = PathFactory.makeTargetState(fromPath);
let globals = <Globals> this.router.globals;
const latestThing = () => globals.transitionHistory.peekTail();
let latest = latestThing();
let $from$ = PathFactory.makeTargetState(fromPath);
let callbackQueue = new Queue<Function>(this.router.stateProvider.invalidCallbacks.slice());
let {$q, $injector} = services;

const invokeCallback = (callback: Function) =>
$q.when($injector.invoke(callback, null, { $to$, $from$ }));
let callbackQueue = new Queue<OnInvalidCallback>(this.invalidCallbacks.slice());
let injector = new ResolveContext(fromPath).injector();

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

function invokeNextCallback() {
let nextCallback = callbackQueue.dequeue();
if (nextCallback === undefined) return Rejection.invalid($to$.error()).toPromise();
return invokeCallback(nextCallback).then(checkForRedirect).then(result => result || invokeNextCallback());
if (nextCallback === undefined) return Rejection.invalid(toState.error()).toPromise();

let callbackResult = services.$q.when(nextCallback(toState, fromState, injector));
return callbackResult.then(checkForRedirect).then(result => result || invokeNextCallback());
}

return invokeNextCallback();
}

/**
* Registers an Invalid State handler
*
* Registers a [[OnInvalidCallback]] function to be invoked when [[StateService.transitionTo]]
* has been called with an invalid state reference parameter
*
* Example:
* ```js
* stateService.onInvalid(function(to, from, injector) {
* if (to.name() === 'foo') {
* let lazyLoader = injector.get('LazyLoadService');
* return lazyLoader.load('foo')
* .then(() => stateService.target('foo'));
* }
* });
* ```
*
* @param {function} callback invoked when the toState is invalid
* This function receives the (invalid) toState, the fromState, and an injector.
* The function may optionally return a [[TargetState]] or a Promise for a TargetState.
* If one is returned, it is treated as a redirect.
*
* @returns a function which deregisters the callback
*/
onInvalid(callback: OnInvalidCallback): Function {
this.invalidCallbacks.push(callback);
return function deregisterListener() {
removeFrom(this.invalidCallbacks)(callback);
}.bind(this);
}


/**
* @ngdoc function
* @name ui.router.state.$state#reload
Expand Down

0 comments on commit 88c6494

Please sign in to comment.