-
Notifications
You must be signed in to change notification settings - Fork 3k
/
Copy pathviews.ts
153 lines (133 loc) · 5.35 KB
/
views.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/** @publicapi @module ng1 */ /** */
import {
StateObject,
pick,
forEach,
tail,
extend,
isArray,
isInjectable,
isDefined,
isString,
services,
trace,
ViewConfig,
ViewService,
ViewConfigFactory,
PathNode,
ResolveContext,
Resolvable,
IInjectable,
} from '@uirouter/core';
import { Ng1ViewDeclaration } from '../interface';
import { TemplateFactory } from '../templateFactory';
/** @internalapi */
export function getNg1ViewConfigFactory(): ViewConfigFactory {
let templateFactory: TemplateFactory = null;
return (path, view) => {
templateFactory = templateFactory || services.$injector.get('$templateFactory');
return [new Ng1ViewConfig(path, view, templateFactory)];
};
}
/** @internalapi */
const hasAnyKey = (keys, obj) => keys.reduce((acc, key) => acc || isDefined(obj[key]), false);
/**
* This is a [[StateBuilder.builder]] function for angular1 `views`.
*
* When the [[StateBuilder]] builds a [[StateObject]] object from a raw [[StateDeclaration]], this builder
* handles the `views` property with logic specific to @uirouter/angularjs (ng1).
*
* If no `views: {}` property exists on the [[StateDeclaration]], then it creates the `views` object
* and applies the state-level configuration to a view named `$default`.
*
* @internalapi
*/
export function ng1ViewsBuilder(state: StateObject) {
// Do not process root state
if (!state.parent) return {};
const tplKeys = ['templateProvider', 'templateUrl', 'template', 'notify', 'async'],
ctrlKeys = ['controller', 'controllerProvider', 'controllerAs', 'resolveAs'],
compKeys = ['component', 'bindings', 'componentProvider'],
nonCompKeys = tplKeys.concat(ctrlKeys),
allViewKeys = compKeys.concat(nonCompKeys);
// Do not allow a state to have both state-level props and also a `views: {}` property.
// A state without a `views: {}` property can declare properties for the `$default` view as properties of the state.
// However, the `$default` approach should not be mixed with a separate `views: ` block.
if (isDefined(state.views) && hasAnyKey(allViewKeys, state)) {
throw new Error(
`State '${state.name}' has a 'views' object. ` +
`It cannot also have "view properties" at the state level. ` +
`Move the following properties into a view (in the 'views' object): ` +
` ${allViewKeys.filter((key) => isDefined(state[key])).join(', ')}`
);
}
const views: { [key: string]: Ng1ViewDeclaration } = {},
viewsObject = state.views || { $default: pick(state, allViewKeys) };
forEach(viewsObject, function (config: Ng1ViewDeclaration, name: string) {
// Account for views: { "": { template... } }
name = name || '$default';
// Account for views: { header: "headerComponent" }
if (isString(config)) config = { component: <string>config };
// Make a shallow copy of the config object
config = extend({}, config);
// Do not allow a view to mix props for component-style view with props for template/controller-style view
if (hasAnyKey(compKeys, config) && hasAnyKey(nonCompKeys, config)) {
throw new Error(
`Cannot combine: ${compKeys.join('|')} with: ${nonCompKeys.join('|')} in stateview: '${name}@${state.name}'`
);
}
config.resolveAs = config.resolveAs || '$resolve';
config.$type = 'ng1';
config.$context = state;
config.$name = name;
const normalized = ViewService.normalizeUIViewTarget(config.$context, config.$name);
config.$uiViewName = normalized.uiViewName;
config.$uiViewContextAnchor = normalized.uiViewContextAnchor;
views[name] = config;
});
return views;
}
/** @hidden */
let id = 0;
/** @internalapi */
export class Ng1ViewConfig implements ViewConfig {
$id = id++;
loaded = false;
controller: Function; // actually IInjectable|string
template: string;
component: string;
locals: any; // TODO: delete me
constructor(public path: PathNode[], public viewDecl: Ng1ViewDeclaration, public factory: TemplateFactory) {}
load() {
const $q = services.$q;
const context = new ResolveContext(this.path);
const params = this.path.reduce((acc, node) => extend(acc, node.paramValues), {});
const promises: any = {
template: $q.when(this.factory.fromConfig(this.viewDecl, params, context)),
controller: $q.when(this.getController(context)),
};
return $q.all(promises).then((results) => {
trace.traceViewServiceEvent('Loaded', this);
this.controller = results.controller;
extend(this, results.template); // Either { template: "tpl" } or { component: "cmpName" }
return this;
});
}
getTemplate = (uiView, context: ResolveContext) =>
this.component
? this.factory.makeComponentTemplate(uiView, context, this.component, this.viewDecl.bindings)
: this.template;
/**
* Gets the controller for a view configuration.
*
* @returns {Function|Promise.<Function>} Returns a controller, or a promise that resolves to a controller.
*/
getController(context: ResolveContext): IInjectable | string | Promise<IInjectable | string> {
const provider = this.viewDecl.controllerProvider;
if (!isInjectable(provider)) return this.viewDecl.controller;
const deps = services.$injector.annotate(provider);
const providerFn = isArray(provider) ? tail(<any>provider) : provider;
const resolvable = new Resolvable('', <any>providerFn, deps);
return resolvable.get(context);
}
}