Skip to content

Commit f639502

Browse files
refactor(view): Put views on a ITransNode for use in Path manipulations
refactor(viewPath): Moved $view.register/reset from transitionSuccess to onStart/onEnter fix($view): Check if ui-view is still in the active list before configuring it
1 parent 6f04572 commit f639502

12 files changed

+82
-63
lines changed

src/path/interface.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import Path from "./../path/path";
33

44
import {IState} from "../state/interface";
55

6+
import {ViewConfig} from "../view/view";
7+
68
import {IRawParams} from "../params/interface";
79
import ParamValues from "../params/paramValues";
810

@@ -19,7 +21,7 @@ export interface IPath extends Path<INode> {}
1921

2022

2123
/** Contains INode base data plus raw params values for the node */
22-
export interface IParamsNode extends INode {
24+
export interface IParamsNode extends INode {
2325
ownParams: IRawParams;
2426
}
2527
/** A Path of IParamsNode(s) */
@@ -33,10 +35,10 @@ export interface IResolveNode extends IParamsNode {
3335
/** A Path of IResolveNode(s) */
3436
export interface IResolvePath extends Path<IResolveNode> {}
3537

36-
3738
/** Contains IResolveNode data, plus a ResolveContext and ParamsValues (bound to a full path) for the node, */
3839
export interface ITransNode extends IResolveNode {
3940
resolveContext: ResolveContext;
41+
views: ViewConfig[];
4042
paramValues: ParamValues;
4143
}
4244
/**

src/path/pathFactory.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import {map, extend, prop, pick, omit, not, objectKeys, curry} from "../common/common";
1+
import {map, extend, pairs, prop, pick, omit, not, objectKeys, curry} from "../common/common";
2+
3+
import {runtime} from "../common/angular1";
24

35
import {IRawParams} from "../params/interface";
46
import ParamValues from "../params/paramValues";
@@ -13,6 +15,9 @@ import Path from "../path/path";
1315

1416
import Resolvable from "../resolve/resolvable";
1517
import ResolveContext from "../resolve/resolveContext";
18+
import PathContext from "../resolve/pathContext";
19+
20+
import {ViewConfig} from "../view/view";
1621

1722
/**
1823
* This class contains functions which convert TargetStates, Nodes and Paths from one type to another.
@@ -99,16 +104,30 @@ export default class PathFactory {
99104
/**
100105
* Given an IResolvePath, upgrades the path to an ITransPath. Each node is assigned a ResolveContext
101106
* and ParamValues object which is bound to the whole path, but closes over the subpath from root to the node.
107+
* The views are also added to the node.
102108
*/
103109
static bindTransNodesToPath(resolvePath: IResolvePath): ITransPath {
104110
let resolveContext = new ResolveContext(resolvePath);
105111
let paramValues = new ParamValues(resolvePath);
106112
let transPath = <ITransPath> resolvePath;
107113

114+
// TODO: this doesn't belong here.
115+
// TODO: pass options to PathContext
116+
// TODO: rename PathContext
117+
function makeViews(node: ITransNode) {
118+
let context = node.state, params = node.paramValues;
119+
let locals = new PathContext(node.resolveContext, node.state, runtime.$injector, {});
120+
const makeViewConfig = ([rawViewName, viewDeclarationObj]) =>
121+
new ViewConfig({rawViewName, viewDeclarationObj, context, locals, params});
122+
return pairs(node.state.views || {}).map(makeViewConfig);
123+
}
124+
108125
// Attach bound resolveContext and paramValues to each node
109-
transPath.nodes().forEach(node => {
126+
// Attach views to each node
127+
transPath.nodes().forEach((node: ITransNode) => {
110128
node.resolveContext = resolveContext.isolateRootTo(node.state);
111129
node.paramValues = paramValues.$isolateRootTo(node.state.name);
130+
node.views = makeViews(node);
112131
}
113132
);
114133

src/state/state.ts

+17
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import PathFactory from "../path/pathFactory";
2222

2323
import {IRawParams, IParamsOrArray} from "../params/interface";
2424

25+
import {ViewConfig} from "../view/view";
2526

2627
/**
2728
* @ngdoc object
@@ -692,10 +693,26 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactoryProvider) {
692693
return $q.reject(transition.error());
693694

694695

696+
// TODO: Move the Transition instance hook registration to its own function
697+
let enteringViews = transition.views("entering");
698+
let exitingViews = transition.views("exiting");
699+
const loadView = (viewConfig: ViewConfig) => $view.load(viewConfig);
700+
const activateView = (viewConfig: ViewConfig) => $view.registerStateViewConfig(viewConfig);
701+
const deactivateView = (viewConfig: ViewConfig) => $view.reset(viewConfig);
702+
703+
const loadAllEnteringViews = () => $q.all(enteringViews.map(loadView)).then(() => undefined);
704+
const deactivateAllExitedViews = () => exitingViews.forEach(deactivateView);
705+
const activateEnteringViews = ($state$: IState) => transition.views("entering", $state$).forEach(activateView);
706+
695707
// Add hooks
696708
// TODO: Move this to its own fn
697709
let hookBuilder = transition.hookBuilder();
710+
711+
transition.onStart({}, loadAllEnteringViews, { priority: 100 });
712+
transition.onStart({}, deactivateAllExitedViews, { priority: 50 });
698713
transition.onStart({}, hookBuilder.getEagerResolvePathFn(), { priority: 100 });
714+
715+
transition.onEnter({}, activateEnteringViews, { priority: 101 });
699716
transition.onEnter({}, hookBuilder.getLazyResolveStateFn(), { priority: 100 });
700717

701718
let onEnterRegistration = (state) => transition.onEnter({to: state.name}, state.onEnter, { priority: -100 });

src/state/stateHandler.ts

-3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ export default class StateHandler {
3030

3131
transitionSuccess(treeChanges: ITreeChanges, transition: Transition) {
3232
let {$view, $state, activeTransQ, changeHistory} = this;
33-
// TODO: sync on entering/exiting state, not transition success?
34-
transition.views("exiting", "from").forEach($view.reset.bind($view));
35-
transition.views("entering", "to").forEach($view.registerStateViewConfig.bind($view));
3633
$view.sync();
3734

3835
// Update globals in $state

src/transition/interface.ts

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface ITransitionHookOptions {
3434
}
3535

3636
export interface ITreeChanges {
37+
[key: string]: ITransPath;
3738
from: ITransPath;
3839
to: ITransPath;
3940
retained: ITransPath;

src/transition/transition.ts

+10-28
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,17 @@ import {HookRegistry, matchState} from "./hookRegistry";
99
import HookBuilder from "./hookBuilder";
1010
import {RejectFactory} from "./rejectFactory";
1111

12-
import {IResolvePath, ITransPath} from "../path/interface";
12+
import {ITransPath} from "../path/interface";
1313
import PathFactory from "../path/pathFactory";
1414

15-
import ResolveContext from "../resolve/resolveContext";
16-
import PathContext from "../resolve/pathContext";
17-
1815
import TargetState from "../state/targetState";
19-
import {IState, IStateDeclaration, IStateViewConfig} from "../state/interface";
16+
import {IState, IStateDeclaration} from "../state/interface";
2017

2118
import ParamValues from "../params/paramValues";
2219

23-
import {extend, flatten, forEach, identity, isEq, isObject, map, not, prop, toJson, unnest, val,
24-
pairs, abstractKey} from "../common/common";
20+
import {ViewConfig} from "../view/view";
21+
22+
import {extend, flatten, forEach, identity, isEq, isObject, not, prop, toJson, val, abstractKey} from "../common/common";
2523

2624
let transitionCount = 0, REJECT = new RejectFactory();
2725
const stateSelf: (_state: IState) => IStateDeclaration = prop("self");
@@ -222,29 +220,13 @@ export class Transition implements IHookRegistry {
222220
return this._treeChanges.retained.states().map(stateSelf);
223221
}
224222

225-
// TODO
226-
context(pathname: string, state: IState): PathContext {
227-
let path = this._treeChanges[pathname].pathFromRootTo(state);
228-
return new PathContext(new ResolveContext(path), state, runtime.$injector, this._options);
229-
}
230-
231223
/**
232-
* Returns a list of StateViewConfig objects;
233-
* Returns one StateViewConfig for each view in each state in a named path of the transition's tree changes
224+
* Returns a list of ViewConfig objects for a given path. Returns one ViewConfig for each view in
225+
* each state in a named path of the transition's tree changes. Optionally limited to a given state in that path.
234226
*/
235-
views(pathname: string = "entering", contextPathname: string = "to") {
236-
let path: IResolvePath = this._treeChanges[pathname];
237-
let states: IState[] = path.states();
238-
let params: ParamValues = this.params();
239-
240-
return unnest(map(states, (state: IState) => {
241-
let context = state;
242-
let locals: PathContext = this.context(contextPathname, state);
243-
const makeViewConfig = ([rawViewName, viewDeclarationObj]) => { return <IStateViewConfig> {
244-
rawViewName, viewDeclarationObj, context, locals, params};
245-
};
246-
return pairs(state.views).map(makeViewConfig);
247-
}));
227+
views(pathname: string = "entering", state?: IState): ViewConfig[] {
228+
let path = this._treeChanges[pathname];
229+
return state ? path.nodeForState(state).views : flatten(path.nodes().map(prop("views")));
248230
}
249231

250232
/**

src/transition/transitionHook.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {defaults, noop, filter, not, isFunction, objectKeys, map, pattern, isEq, val, pipe, eq, is, isPromise, isObject, parse} from "../common/common";
1+
import {defaults, noop, filter, not, isFunction, isDefined, objectKeys, map, pattern, isEq, val, pipe, eq, is, isPromise, isObject, parse} from "../common/common";
22
import trace from "../common/trace";
33
import {RejectFactory} from "./rejectFactory";
44
import {Transition} from "./transition";
@@ -76,16 +76,20 @@ export default class TransitionHook {
7676
mapNewResolves(resolves: IResolveDeclarations) {
7777
let invalid = filter(resolves, not(isFunction)), keys = objectKeys(invalid);
7878
if (keys.length)
79-
throw new Error("Invalid resolve key/value: ${keys[0]}/${invalid[keys[0]]}");
79+
throw new Error(`Invalid resolve key/value: ${keys[0]}/${invalid[keys[0]]}`);
8080

8181
// If result is an object, it should be a map of strings to functions.
8282
const makeResolvable = (fn, name) => new Resolvable(name, fn, this.state);
8383
return map(resolves, makeResolvable);
8484
}
8585

8686
handleHookResult(hookResult) {
87+
if (!isDefined(hookResult)) return undefined;
88+
if (this.options.trace) trace.traceHookResult(hookResult, undefined, this.options);
89+
8790
let transitionResult = this.mapHookResult(hookResult);
88-
if (this.options.trace) trace.traceHookResult(hookResult, transitionResult, this.options);
91+
if (transitionResult && this.options.trace) trace.traceHookResult(hookResult, transitionResult, this.options);
92+
8993
return transitionResult;
9094
}
9195

src/view/templateFactory.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ function $TemplateFactory( $http, $templateCache) {
106106
* for that string.
107107
*/
108108
this.fromProvider = function (provider, params, locals) {
109-
return locals(provider, { params: params });
109+
return locals(provider, { $stateParams: params });
110110
};
111111
}
112112

src/view/view.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ function $View( $rootScope, $templateFactory, $q, $timeout) {
196196
return $q.all(viewConfig.promises).then((results) => {
197197
debug(`$view.ViewConfig: Loaded ${viewConfigString(viewConfig)}`);
198198
extend(viewConfig, results);
199-
this.sync();
200199
});
201200
};
202201

@@ -206,19 +205,14 @@ function $View( $rootScope, $templateFactory, $q, $timeout) {
206205
* @param {String} name The fully-qualified name of the view to reset.
207206
* @return {Boolean} Returns `true` if the view exists, otherwise `false`.
208207
*/
209-
this.reset = function reset (stateViewConfig) {
210-
let viewConfig = new ViewConfig(stateViewConfig);
208+
this.reset = function reset (viewConfig) {
211209
debug(`$view.ViewConfig: <- Removing ${viewConfigString(viewConfig)}`);
212-
// TODO: Check if this code is doing the right thing
213210
viewConfigs.filter(match(viewConfig, "uiViewName", "context")).forEach(removeFrom(viewConfigs));
214-
//uiViews.filter(match(viewConfig, "name", "parentContext")).forEach(uiView => uiView.configUpdated(null));
215211
};
216212

217-
this.registerStateViewConfig = function(stateViewConfig: IStateViewConfig) {
218-
let viewConfig = new ViewConfig(stateViewConfig);
213+
this.registerStateViewConfig = function(viewConfig: ViewConfig) {
219214
debug(`$view.ViewConfig: -> Registering ${viewConfigString(viewConfig)}`);
220215
viewConfigs.push(viewConfig);
221-
this.load(viewConfig);
222216
};
223217

224218
this.sync = () => {
@@ -318,7 +312,13 @@ function $View( $rootScope, $templateFactory, $q, $timeout) {
318312
return [uiView, matchingConfigs[0]];
319313
};
320314

321-
const configureUiView = ([uiView, viewConfig]) => uiView.configUpdated(viewConfig);
315+
const configureUiView = ([uiView, viewConfig]) => {
316+
// If a parent ui-view is reconfigured, it could destroy child ui-views.
317+
// Before configuring a child ui-view, make sure it's still in the active uiViews array.
318+
if (uiViews.indexOf(uiView) !== -1)
319+
uiView.configUpdated(viewConfig);
320+
};
321+
322322
uiViews.sort(depthCompare(uiViewDepth, 1)).map(matchingConfigPair).forEach(configureUiView);
323323
};
324324

src/view/viewDirective.ts

+6-9
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,7 @@ function $ViewDirective( $view, $animate, $uiViewScroll, $interpolate,
143143
}
144144

145145
function configsEqual(config1, config2) {
146-
return (config1 === config2) || (config1 && config2 && (
147-
config1.controller === config2.controller &&
148-
config1.template === config2.template &&
149-
config1.controllerAs === config2.controllerAs
150-
));
146+
return config1 === config2;
151147
}
152148

153149
let directive = {
@@ -163,7 +159,7 @@ function $ViewDirective( $view, $animate, $uiViewScroll, $interpolate,
163159
onloadExp = attrs.onload || '',
164160
autoScrollExp = attrs.autoscroll,
165161
renderer = getRenderer(attrs, scope),
166-
viewConfig = {},
162+
viewConfig = undefined,
167163
inherited = $element.inheritedData('$uiView') || { context: $view.rootContext() },
168164
name = $interpolate(attrs.uiView || attrs.name || '')(scope) || '$default';
169165

@@ -179,10 +175,11 @@ function $ViewDirective( $view, $animate, $uiViewScroll, $interpolate,
179175

180176
debug(`uiView tag: Linking '${viewData.fqn}#${viewData.id}'`);
181177

182-
function configUpdatedCallback(config: ViewConfig = <any> {}) {
178+
function configUpdatedCallback(config?: ViewConfig) {
183179
if (configsEqual(viewConfig, config)) return;
184-
debug(`uiView tag: Updating '${viewData.fqn}#${viewData.id}': (${uiViewString(viewData)}) with ViewConfig from context='${config.context}'`);
185-
viewConfig = extend({}, config);
180+
let context = config && config.context;
181+
debug(`uiView tag: Updating '${viewData.fqn}#${viewData.id}': (${uiViewString(viewData)}) with ViewConfig from context='${context}'`);
182+
viewConfig = config;
186183
updateView(config);
187184
}
188185

test/stateDirectivesSpec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ describe('uiStateRef', function() {
101101
}
102102
};
103103

104-
xdescribe('links', function() {
104+
describe('links', function() {
105105
beforeEach(inject(buildDOM));
106106

107107
it('should generate the correct href', function() {

test/viewDirectiveSpec.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -463,31 +463,31 @@ describe('uiView', function () {
463463
// Enter Animation
464464
animation = $animate.queue.shift();
465465
expect(animation.event).toBe('enter');
466-
expect(animation.element.text()).toBe(content);
466+
expect(animation.element.text() + "-1").toBe(content + "-1");
467467

468468
$state.transitionTo(aState);
469469
$q.flush();
470470

471471
// Enter Animation
472472
animation = $animate.queue.shift();
473473
expect(animation.event).toBe('enter');
474-
expect(animation.element.text()).toBe(aState.template);
474+
expect(animation.element.text() + "-2").toBe(aState.template + "-2");
475475
// Leave Animation
476476
animation = $animate.queue.shift();
477477
expect(animation.event).toBe('leave');
478-
expect(animation.element.text()).toBe(content);
478+
expect(animation.element.text() + "-3").toBe(content + "-3");
479479

480480
$state.transitionTo(bState);
481481
$q.flush();
482482

483483
// Enter Animation
484484
animation = $animate.queue.shift();
485485
expect(animation.event).toBe('enter');
486-
expect(animation.element.text()).toBe(bState.template);
486+
expect(animation.element.text() + "-4").toBe(bState.template + "-4");
487487
// Leave Animation
488488
animation = $animate.queue.shift();
489489
expect(animation.event).toBe('leave');
490-
expect(animation.element.text()).toBe(aState.template);
490+
expect(animation.element.text() + "-5").toBe(aState.template + "-5");
491491

492492
// No more animations
493493
expect($animate.queue.length).toBe(0);

0 commit comments

Comments
 (0)