Skip to content

Commit 1954495

Browse files
refactor($view): pre-load all controller locals during transition hook
1 parent 9aeb9ad commit 1954495

12 files changed

+81
-132
lines changed

src/common/angular1.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ export let runtime: IRuntime = {
2727
* - Calls $injector.instantiate with controller constructor
2828
* - Annotate constructor
2929
* - Undecorate $injector
30+
*
31+
* returns an array of strings, which are the arguments of the controller expression
3032
*/
3133

32-
export function annotateController(controllerExpression) {
34+
export function annotateController(controllerExpression): string[] {
3335
let $injector = runtime.$injector;
3436
let $controller = $injector.get("$controller");
3537
let oldInstantiate = $injector.instantiate;

src/path/pathFactory.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -110,13 +110,10 @@ export default class PathFactory {
110110
let transPath = <ITransPath> resolvePath;
111111

112112
// TODO: this doesn't belong here.
113-
// TODO: pass options to PathContext
114-
// TODO: rename PathContext
115113
function makeViews(node: ITransNode) {
116114
let context = node.state, params = node.paramValues;
117-
let locals = new PathContext(node.resolveContext, node.state, runtime.$injector, {});
118115
const makeViewConfig = ([rawViewName, viewDeclarationObj]) =>
119-
new ViewConfig({rawViewName, viewDeclarationObj, context, locals, params});
116+
new ViewConfig({rawViewName, viewDeclarationObj, context, params});
120117
return pairs(node.state.views || {}).map(makeViewConfig);
121118
}
122119

src/resolve/pathContext.ts

-30
This file was deleted.

src/resolve/resolveContext.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export default class ResolveContext {
115115
* @param locals: are the angular $injector-style locals to inject
116116
* @param options: options (TODO: document)
117117
*/
118-
invokeLater(state: IState, fn: IInjectable, locals: any, options: IOptions1 = {}): IPromise<any> {
118+
invokeLater(state: IState, fn: IInjectable, locals: any = {}, options: IOptions1 = {}): IPromise<any> {
119119
let isolateCtx = this.isolateRootTo(state);
120120
let resolvables = this.getResolvablesForFn(fn, isolateCtx);
121121
trace.tracePathElementInvoke(state, fn, Object.keys(resolvables), extend({when: "Later"}, options));

src/resolve/resolveInjector.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1+
import {map} from "../common/common";
12
import ResolveContext from "../resolve/resolveContext";
23
import {IState} from "../state/interface";
4+
import Resolvable from "./resolvable";
35

46
export default class ResolveInjector {
57
constructor(private _resolveContext: ResolveContext, private _state: IState) { }
6-
/** Invokes an annotated function in the context of the toPath */
8+
9+
/** Returns a promise to invoke an annotated function in the resolve context */
710
invokeLater(injectedFn, locals) {
811
return this._resolveContext.invokeLater(this._state, injectedFn, locals);
912
}
13+
14+
/** Invokes an annotated function in the resolve context */
1015
invokeNow(injectedFn, locals) {
1116
return this._resolveContext.invokeNow(this._state, injectedFn, locals);
1217
}
18+
19+
/** Returns the a promise for locals (realized Resolvables) that a function wants */
20+
getLocals(injectedFn) {
21+
const resolve = (r: Resolvable) => r.get(this._resolveContext);
22+
return map(this._resolveContext.getResolvablesForFn(injectedFn), resolve);
23+
}
1324
}

src/state/interface.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {IRawParams, IParamsOrArray} from "../params/interface";
66
import ParamSet from "../params/paramSet";
77

88
import {IContextRef} from "../view/interface";
9-
import PathContext from "../resolve/pathContext";
109

1110
import TargetState from "./targetState";
1211

@@ -18,10 +17,9 @@ export type IStateOrName = (string|IStateDeclaration|IState);
1817
/** Context obj, State-view definition, transition params */
1918
export interface IStateViewConfig {
2019
viewDeclarationObj: IViewDeclaration; // A view block from a state config
21-
rawViewName: string; // The name of the view block
22-
params: any; // State params?
23-
context: IContextRef; // The context object reference this ViewConfig belongs to
24-
locals: PathContext; // The Resolve context (rename class!)
20+
rawViewName: string; // The name of the view block
21+
params: any; // State params?
22+
context: IContextRef; // The context object reference this ViewConfig belongs to
2523
}
2624

2725
/** View declaration inside state declaration */

src/state/state.ts

+27-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {extend, defaults, copy, equalForKeys, forEach, ancestors, noop, isDefined, isObject, isString} from "../common/common";
22
import Queue from "../common/queue";
33
import {IServiceProviderFactory, IPromise} from "angular";
4+
import {annotateController} from "../common/angular1";
45

56
import {IStateService, IState, IStateDeclaration, IStateOrName, IHrefOptions} from "./interface";
67
import Glob from "./glob";
@@ -693,30 +694,45 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactoryProvider) {
693694
if (!transition.valid())
694695
return $q.reject(transition.error());
695696

697+
let stateHandler = new StateHandler($urlRouter, $view, $state, $stateParams, $q, transQueue, treeChangesQueue);
698+
let hookBuilder = transition.hookBuilder();
696699

697700
// TODO: Move the Transition instance hook registration to its own function
701+
// Add hooks
698702
let enteringViews = transition.views("entering");
699703
let exitingViews = transition.views("exiting");
704+
let treeChanges = transition.treeChanges();
700705

701706
function $updateViews() {
702707
exitingViews.forEach((viewConfig: ViewConfig) => $view.reset(viewConfig));
703708
enteringViews.forEach((viewConfig: ViewConfig) => $view.registerStateViewConfig(viewConfig));
709+
$view.sync();
704710
}
705711

706712
function $loadAllEnteringViews() {
707-
const loadView = (viewConfig: ViewConfig) => $view.load(viewConfig);
713+
const loadView = (vc: ViewConfig) => {
714+
let resolveInjector = treeChanges.to.nodeForState(vc.context.name).resolveInjector;
715+
return $view.load(vc, resolveInjector);
716+
};
708717
return $q.all(enteringViews.map(loadView)).then(noop);
709718
}
710719

711-
let stateHandler = new StateHandler($urlRouter, $view, $state, $stateParams, $q, transQueue, treeChangesQueue);
712-
// Add hooks
713-
// TODO: Move this to its own fn
714-
let hookBuilder = transition.hookBuilder();
720+
function $loadAllControllerLocals() {
721+
const loadLocals = (vc: ViewConfig) => {
722+
let deps = annotateController(vc.controller);
723+
let resolveInjector = treeChanges.to.nodeForState(vc.context.name).resolveInjector;
724+
function $loadControllerLocals() { }
725+
$loadControllerLocals.$inject = deps;
726+
return $q.all(resolveInjector.getLocals($loadControllerLocals)).then((locals) => vc.locals = locals);
727+
};
728+
729+
let loadAllLocals = enteringViews.filter(vc => !!vc.controller).map(loadLocals);
730+
return $q.all(loadAllLocals).then(noop);
731+
}
715732

716733
transition.onStart({}, hookBuilder.getEagerResolvePathFn(), { priority: 1000 });
717734
transition.onEnter({}, hookBuilder.getLazyResolveStateFn(), { priority: 1000 });
718735

719-
transition.onError({}, $transitions.defaultErrorHandler());
720736

721737
let onEnterRegistration = (state) => transition.onEnter({to: state.name}, state.onEnter);
722738
transition.entering().filter(state => !!state.onEnter).forEach(onEnterRegistration);
@@ -727,10 +743,13 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactoryProvider) {
727743
let onExitRegistration = (state) => transition.onExit({from: state.name}, state.onExit);
728744
transition.exiting().filter(state => !!state.onExit).forEach(onExitRegistration);
729745

730-
if (enteringViews.length)
746+
if (enteringViews.length) {
731747
transition.onStart({}, $loadAllEnteringViews);
748+
transition.onFinish({}, $loadAllControllerLocals);
749+
}
732750
if (exitingViews.length || enteringViews.length)
733-
transition.onFinish({}, $updateViews);
751+
transition.onSuccess({}, $updateViews);
752+
transition.onError({}, $transitions.defaultErrorHandler());
734753

735754
// Commit global state data as the last hook in the transition (using a very low priority onFinish hook)
736755
function $commitGlobalData() { stateHandler.transitionSuccess(transition.treeChanges(), transition); }

src/ui-router.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ import Path from "./path/path"
1313
import PathFactory from "./path/pathFactory"
1414
var path = { Path, PathFactory }
1515

16-
import PathContext from "./resolve/pathContext"
16+
import ResolveInjector from "./resolve/resolveInjector"
1717
import Resolvable from "./resolve/resolvable"
1818
import ResolveContext from "./resolve/resolveContext"
19-
var resolve = { Path, PathContext, Resolvable, ResolveContext };
19+
var resolve = { Path, ResolveInjector, Resolvable, ResolveContext };
2020

2121
import Glob from "./state/glob";
2222
import * as stateInterface from "./state/interface";

src/view/templateFactory.ts

+9-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// <reference path='../../typings/angularjs/angular.d.ts' />
2-
import {isDefined, isFunction} from "../common/common";
2+
import {isDefined, isFunction, prop} from "../common/common";
33

44
/**
55
* @ngdoc object
@@ -34,17 +34,17 @@ function $TemplateFactory( $http, $templateCache) {
3434
* @param {Function} config.templateProvider function to invoke via
3535
* {@link ui.router.util.$templateFactory#fromProvider fromProvider}.
3636
* @param {object} params Parameters to pass to the template function.
37-
* @param {Function} locals Function to which an injectable function may be passed.
38-
* via a `templateProvider`. Defaults to `{ params: params }`.
37+
* @param {Function} injectFn Function to which an injectable function may be passed.
38+
* If templateProvider is defined, this injectFn will be used to invoke it.
3939
*
4040
* @return {string|object} The template html as a string, or a promise for
4141
* that string,or `null` if no template is configured.
4242
*/
43-
this.fromConfig = function (config, params, locals) {
43+
this.fromConfig = function (config, params, injectFn) {
4444
return (
4545
isDefined(config.template) ? this.fromString(config.template, params) :
4646
isDefined(config.templateUrl) ? this.fromUrl(config.templateUrl, params) :
47-
isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, locals) :
47+
isDefined(config.templateProvider) ? this.fromProvider(config.templateProvider, params, injectFn) :
4848
null
4949
);
5050
};
@@ -85,9 +85,7 @@ function $TemplateFactory( $http, $templateCache) {
8585
this.fromUrl = function (url, params) {
8686
if (isFunction(url)) url = url(params);
8787
if (url == null) return null;
88-
else return $http
89-
.get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
90-
.then(function(response) { return response.data; });
88+
return $http.get(url, { cache: $templateCache, headers: { Accept: 'text/html' }}).then(prop("data"));
9189
};
9290

9391
/**
@@ -100,13 +98,12 @@ function $TemplateFactory( $http, $templateCache) {
10098
*
10199
* @param {Function} provider Function to invoke via `locals`
102100
* @param {Object} params Parameters for the template.
103-
* @param {Object} locals Locals to pass to `invoke`. Defaults to
104-
* `{ params: params }`.
101+
* @param {Function} injectFn a function used to invoke the template provider
105102
* @return {string|Promise.<string>} The template html as a string, or a promise
106103
* for that string.
107104
*/
108-
this.fromProvider = function (provider, params, locals) {
109-
return locals(provider, { $stateParams: params });
105+
this.fromProvider = function (provider, params, injectFn) {
106+
return injectFn(provider, { $stateParams: params });
110107
};
111108
}
112109

src/view/view.ts

+11-50
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"use strict";
22
/// <reference path='../../typings/angularjs/angular.d.ts' />
3-
import {IPromise} from "angular";
4-
import {isInjectable, isString, defaults, extend, curry, addPairToObj, prop, pick, removeFrom, isEq, val, TypedMap} from "../common/common";
3+
import {isInjectable, isString, extend, curry, addPairToObj, prop, pick, removeFrom, isEq, val, TypedMap} from "../common/common";
54
import trace from "../common/trace";
65
import {IStateViewConfig, IViewDeclaration} from "../state/interface";
76
import {IUiViewData, IContextRef} from "./interface";
7+
import ResolveInjector from "../resolve/resolveInjector";
88

99
/**
1010
* Given a raw view name from a views: config, returns a normalized target viewName and contextAnchor
@@ -43,10 +43,6 @@ function normalizeUiViewTarget(rawViewName = "") {
4343
*/
4444
export class ViewConfig {
4545
viewDeclarationObj: IViewDeclaration;
46-
promises: {
47-
template: IPromise<string>,
48-
controller: IPromise<Function>
49-
};
5046

5147
template: string;
5248
controller: Function;
@@ -86,9 +82,8 @@ export class ViewConfig {
8682
return !!(viewDef.template || viewDef.templateUrl || viewDef.templateProvider);
8783
}
8884

89-
getTemplate($factory) {
90-
let locals = this.locals, viewDef = this.viewDeclarationObj;
91-
return $factory.fromConfig(viewDef, this.params, locals.invoke.bind(locals));
85+
getTemplate($factory, injector: ResolveInjector) {
86+
return $factory.fromConfig(this.viewDeclarationObj, this.params, injector.invokeLater.bind(injector));
9287
}
9388

9489
/**
@@ -97,10 +92,10 @@ export class ViewConfig {
9792
*
9893
* @returns {Function|Promise.<Function>} Returns a controller, or a promise that resolves to a controller.
9994
*/
100-
getController() {
95+
getController(injector: ResolveInjector) {
10196
//* @param {Object} locals A context object from transition.context() to invoke a function in the correct context
10297
let provider = this.viewDeclarationObj.controllerProvider;
103-
return isInjectable(provider) ? this.locals.invoke(provider) : this.viewDeclarationObj.controller;
98+
return isInjectable(provider) ? injector.invokeLater(provider, {}) : this.viewDeclarationObj.controller;
10499
}
105100
}
106101

@@ -143,50 +138,16 @@ function $View( $rootScope, $templateFactory, $q, $timeout) {
143138
* `$templateFactory.fromConfig()`, including `params` and `locals`.
144139
* @return {Promise.<string>} Returns a promise that resolves to the value of the template loaded.
145140
*/
146-
this.load = function load (viewConfig: ViewConfig, options) {
147-
options = defaults(options, {
148-
context: null,
149-
parent: null,
150-
notify: true,
151-
async: true,
152-
params: {}
153-
});
154-
141+
this.load = function load (viewConfig: ViewConfig, injector: ResolveInjector) {
155142
if (!viewConfig.hasTemplate())
156143
throw new Error(`No template configuration specified for '${viewConfig.uiViewName}@${viewConfig.uiViewContextAnchor}'`);
157144

158-
if (options.notify) {
159-
/**
160-
* @ngdoc event
161-
* @name ui.router.state.$state#$viewContentLoading
162-
* @eventOf ui.router.state.$view
163-
* @eventType broadcast on root scope
164-
* @description
165-
*
166-
* Fired once the view **begins loading**, *before* the DOM is rendered.
167-
*
168-
* @param {Object} event Event object.
169-
* @param {Object} viewConfig The view config properties (template, controller, etc).
170-
*
171-
* @example
172-
*
173-
* <pre>
174-
* $scope.$on('$viewContentLoading', function(event, viewConfig) {
175-
* // Access to all the view config properties.
176-
* // and one special property 'targetView'
177-
* // viewConfig.targetView
178-
* });
179-
* </pre>
180-
*/
181-
$rootScope.$broadcast('$viewContentLoading', extend({ targetView: name }, options));
182-
}
183-
184-
viewConfig.promises = {
185-
template: $q.when(viewConfig.getTemplate($templateFactory)),
186-
controller: $q.when(viewConfig.getController())
145+
let promises = {
146+
template: $q.when(viewConfig.getTemplate($templateFactory, injector)),
147+
controller: $q.when(viewConfig.getController(injector))
187148
};
188149

189-
return $q.all(viewConfig.promises).then((results) => {
150+
return $q.all(promises).then((results) => {
190151
trace.traceViewServiceEvent("Loaded", viewConfig);
191152
return extend(viewConfig, results);
192153
});

0 commit comments

Comments
 (0)