Skip to content

Commit

Permalink
feat(UIRouterGlobals): Create UIRouterGlobals
Browse files Browse the repository at this point in the history
Created an isolated place to hold the public global mutable state
fix(UIRouterGlobals): Only store the last successful transition and the last attempted transition
closes #2525
  • Loading branch information
christopherthielen committed Apr 2, 2016
1 parent de252a0 commit 0eb7406
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 88 deletions.
6 changes: 4 additions & 2 deletions src/common/queue.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/** @module common */ /** for typedoc */

export class Queue<T> {
constructor(private _items: T[] = []) { }
constructor(private _items: T[] = [], private _limit: number = null) { }

enqueue(item: T) {
this._items.push(item);
let items = this._items;
items.push(item);
if (this._limit && items.length > this._limit) items.shift();
return item;
}

Expand Down
72 changes: 72 additions & 0 deletions src/globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/** @module common */ /** */
import {StateParams} from "./params/stateParams";
import {StateDeclaration} from "./state/interface";
import {State} from "./state/stateObject";
import {Transition} from "./transition/transition";
import {Queue} from "./common/queue";
import {TransitionService} from "./transition/transitionService";
import {copy} from "./common/common";

/**
* Global mutable state
*/
export class UIRouterGlobals {
/**
* Current parameter values
*
* The parameter values from the latest successful transition
*/
params: StateParams = new StateParams();
/**
* Current state
*
* The to-state from the latest successful transition
*/
current: StateDeclaration;
/**
* Current state
*
* The to-state from the latest successful transition
*/
$current: State;
/**
* The current transition (in progress)
*/
transition: Transition;
/**
* The transition history
*
* This queue's size is limited to a maximum number (default: 1)
*/
transitionHistory = new Queue<Transition>([], 1);
/**
* The history of successful transitions
*
* This queue's size is limited to a maximum number (default: 1)
*/
successfulTransitions = new Queue<Transition>([], 1);

constructor(transitionService: TransitionService) {
const beforeNewTransition = ($transition$) => {

this.transition = $transition$;
this.transitionHistory.enqueue($transition$);

const updateGlobalState = () => {
this.successfulTransitions.enqueue($transition$);
this.$current = $transition$.$to();
this.current = this.$current.self;
copy($transition$.params(), this.params);
};

$transition$.onSuccess({}, updateGlobalState, {priority: 10000});

const clearCurrentTransition = () => { if (this.transition === $transition$) this.transition = null; };

$transition$.promise.finally(clearCurrentTransition)

};

transitionService.onBefore({}, ['$transition$', beforeNewTransition]);
}
}
7 changes: 4 additions & 3 deletions src/ng1/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ const resolveFactory = () => ({
}
});

function $stateParamsFactory(ng1UIRouter, $rootScope) {
return router.stateParams;
function $stateParamsFactory(ng1UIRouter) {
return ng1UIRouter.globals.params;
}

// The 'ui.router' ng1 module depends on 'ui.router.init' module.
Expand Down Expand Up @@ -265,7 +265,8 @@ angular.module('ui.router.state').provider('$state', ['ng1UIRouterProvider', get
angular.module('ui.router.state').run(['$state', function($state) { }]);

// $stateParams service
angular.module('ui.router.state').factory('$stateParams', ['ng1UIRouter', '$rootScope', $stateParamsFactory]);
angular.module('ui.router.state').factory('$stateParams', ['ng1UIRouter', (ng1UIRouter) =>
ng1UIRouter.globals.params]);

// $transitions service and $transitionsProvider
function getTransitionsProvider() {
Expand Down
3 changes: 0 additions & 3 deletions src/params/stateParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,3 @@ export class StateParams {
};
}

export function stateParamsFactory() {
return new StateParams();
}
20 changes: 11 additions & 9 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import {UrlMatcherFactory} from "./url/urlMatcherFactory";
import {UrlRouterProvider} from "./url/urlRouter";
import {StateProvider} from "./state/state";
import {stateParamsFactory, StateParams} from "./params/stateParams";
import {UrlRouter} from "./url/urlRouter";
import {TransitionService} from "./transition/transitionService";
import {ViewService} from "./view/view";
import {StateRegistry} from "./state/stateRegistry";
import {StateService} from "./state/stateService";
import {UIRouterGlobals} from "./globals";

/**
* The master class used to instantiate an instance of UI-Router.
Expand All @@ -19,27 +19,29 @@ import {StateService} from "./state/stateService";
* the URL by calling `urlRouter.listen()` ([[URLRouter.listen]])
*/
export class UIRouter {
viewService = new ViewService();

transitionService: TransitionService = new TransitionService(this.viewService);

stateParams: StateParams = stateParamsFactory();
globals: UIRouterGlobals = new UIRouterGlobals(this.transitionService);

urlMatcherFactory: UrlMatcherFactory = new UrlMatcherFactory();

urlRouterProvider: UrlRouterProvider = new UrlRouterProvider(this.urlMatcherFactory, this.stateParams);
urlRouterProvider: UrlRouterProvider = new UrlRouterProvider(this.urlMatcherFactory, this.globals.params);

urlRouter: UrlRouter = new UrlRouter(this.urlRouterProvider);

viewService = new ViewService();

transitionService: TransitionService = new TransitionService(this.viewService);

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

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

stateService = new StateService(this.viewService, this.stateParams, this.urlRouter, this.transitionService, this.stateRegistry, this.stateProvider);
stateService = new StateService(this.viewService, this.urlRouter, this.transitionService, this.stateRegistry, this.stateProvider, this.globals);

constructor() {
this.viewService.rootContext(this.stateRegistry.root());
this.globals.$current = this.stateRegistry.root();
this.globals.current = this.globals.$current.self;
}
}

58 changes: 18 additions & 40 deletions src/state/hooks/transitionManager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/** @module state */ /** for typedoc */
import {copy} from "../../common/common";
import {prop} from "../../common/hof";
import {Queue} from "../../common/queue";
import {Param} from "../../params/param";

import {TreeChanges} from "../../transition/interface";
Expand All @@ -15,6 +13,8 @@ import {ViewHooks} from "./viewHooks";
import {EnterExitHooks} from "./enterExitHooks";
import {ResolveHooks} from "./resolveHooks";
import {UrlRouter} from "../../url/urlRouter";
import {services} from "../../common/coreservices";
import {UIRouterGlobals} from "../../globals";

/**
* This class:
Expand All @@ -38,18 +38,17 @@ export class TransitionManager {
private enterExitHooks: EnterExitHooks;
private viewHooks: ViewHooks;
private resolveHooks: ResolveHooks;
private $q;

constructor(
private transition: Transition,
private $transitions,
private $urlRouter: UrlRouter,
private $view, // service
private $state: StateService,
private $stateParams, // service/obj
private $q, // TODO: get from runtime.$q
private activeTransQ: Queue<Transition>,
private changeHistory: Queue<TreeChanges>
private globals: UIRouterGlobals
) {
this.$q = services.$q;
this.viewHooks = new ViewHooks(transition, $view);
this.enterExitHooks = new EnterExitHooks(transition);
this.resolveHooks = new ResolveHooks(transition);
Expand All @@ -63,45 +62,26 @@ export class TransitionManager {
}

runTransition(): Promise<any> {
this.activeTransQ.clear(); // TODO: nuke this
this.activeTransQ.enqueue(this.transition);
this.$state.transition = this.transition;
let promise = this.transition.run()
this.globals.transitionHistory.enqueue(this.transition);
return this.transition.run()
.then((trans: Transition) => trans.to()) // resolve to the final state (TODO: good? bad?)
.catch(error => this.transRejected(error)); // if rejected, handle dynamic and redirect

let always = () => {
this.activeTransQ.remove(this.transition);
if (this.$state.transition === this.transition) this.transition = null;
};

promise.then(always, always);

return promise;
}

registerUpdateGlobalState() {
this.transition.onFinish({}, this.updateGlobalState.bind(this), {priority: -10000});
}

updateGlobalState() {
let {treeChanges, transition, $state, changeHistory} = this;
// Update globals in $state
$state.$current = transition.$to();
$state.current = $state.$current.self;
changeHistory.enqueue(treeChanges);
this.updateStateParams();
// After globals.current is updated at priority: 10000
this.transition.onSuccess({}, this.updateUrl.bind(this), {priority: 9999});
}

transRejected(error): (StateDeclaration|Promise<any>) {
let {transition, $state, $stateParams, $q} = this;
let {transition, $state, $q} = this;
// Handle redirect and abort
if (error instanceof TransitionRejection) {
if (error.type === RejectType.IGNORED) {
// Update $stateParmas/$state.params/$location.url if transition ignored, but dynamic params have changed.
let dynamic = $state.$current.parameters().filter(prop('dynamic'));
if (!Param.equals(dynamic, $stateParams, transition.params())) {
this.updateStateParams();
if (!Param.equals(dynamic, $state.params, transition.params())) {
this.updateUrl();
}
return $state.current;
}
Expand All @@ -120,22 +100,20 @@ export class TransitionManager {
return $q.reject(error);
}

updateStateParams() {
updateUrl() {
let transition = this.transition;
let {$urlRouter, $state, $stateParams} = this;
let {$urlRouter, $state} = this;
let options = transition.options();
copy(transition.params(), $state.params);
copy($state.params, $stateParams);
var toState = transition.$to();

if (options.location && $state.$current.navigable) {
$urlRouter.push($state.$current.navigable.url, $stateParams, { replace: options.location === 'replace' });
$urlRouter.push($state.$current.navigable.url, $state.params, { replace: options.location === 'replace' });
}

$urlRouter.update(true);
}

private _redirectMgr(redirect: Transition): TransitionManager {
let {$transitions, $urlRouter, $view, $state, $stateParams, $q, activeTransQ, changeHistory} = this;
return new TransitionManager(redirect, $transitions, $urlRouter, $view, $state, $stateParams, $q, activeTransQ, changeHistory);
let {$transitions, $urlRouter, $view, $state, globals} = this;
return new TransitionManager(redirect, $transitions, $urlRouter, $view, $state, globals);
}
}
Loading

0 comments on commit 0eb7406

Please sign in to comment.