-
Notifications
You must be signed in to change notification settings - Fork 3k
/
Copy pathviewsBuilder.ts
150 lines (129 loc) · 6.37 KB
/
viewsBuilder.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
/** @module ng1 */ /** */
import {State} from "../state/stateObject";
import {pick, forEach, anyTrueR, unnestR} from "../common/common";
import {kebobString} from "../common/strings";
import {ViewConfig} from "../view/interface";
import {Ng1ViewDeclaration} from "./interface";
import {ViewService} from "../view/view";
import {isInjectable, isDefined, isString, isObject} from "../common/predicates";
import {services} from "../common/coreservices";
import {trace} from "../common/trace";
import {Node} from "../path/node";
import {TemplateFactory} from "./templateFactory";
import {ResolveContext} from "../resolve/resolveContext";
export const ng1ViewConfigFactory = (node, view) => new Ng1ViewConfig(node, view);
/**
* This is a [[StateBuilder.builder]] function for angular1 `views`.
*
* When the [[StateBuilder]] builds a [[State]] object from a raw [[StateDeclaration]], this builder
* handles the `views` property with logic specific to angular-ui-router (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`.
*/
export function ng1ViewsBuilder(state: State) {
let tplKeys = ['templateProvider', 'templateUrl', 'template', 'notify', 'async'],
ctrlKeys = ['controller', 'controllerProvider', 'controllerAs', 'resolveAs'],
compKeys = ['component', 'bindings'],
nonCompKeys = tplKeys.concat(ctrlKeys),
allKeys = compKeys.concat(nonCompKeys);
let views = {}, viewsObject = state.views || {"$default": pick(state, allKeys)};
forEach(viewsObject, function (config: Ng1ViewDeclaration, name) {
// Account for views: { "": { template... } }
name = name || "$default";
// Account for views: { header: "headerComponent" }
if (isString(config)) config = { component: <string> config };
if (!Object.keys(config).length) return;
// Configure this view for routing to an angular 1.5+ style .component (or any directive, really)
if (config.component) {
if (nonCompKeys.map(key => isDefined(config[key])).reduce(anyTrueR, false)) {
throw new Error(`Cannot combine: ${compKeys.join("|")} with: ${nonCompKeys.join("|")} in stateview: 'name@${state.name}'`);
}
// Dynamically build a template like "<component-name input1='$resolve.foo'></component-name>"
config.templateProvider = ['$injector', function($injector) {
const resolveFor = key => config.bindings && config.bindings[key] || key;
const prefix = angular.version.minor >= 3 ? "::" : "";
let attrs = getComponentInputs($injector, config.component)
.map(key => `${kebobString(key)}='${prefix}$resolve.${resolveFor(key)}'`).join(" ");
let kebobName = kebobString(config.component);
return `<${kebobName} ${attrs}></${kebobName}>`;
}];
}
config.resolveAs = config.resolveAs || '$resolve';
config.$type = "ng1";
config.$context = state;
config.$name = name;
let normalized = ViewService.normalizeUiViewTarget(config.$context, config.$name);
config.$uiViewName = normalized.uiViewName;
config.$uiViewContextAnchor = normalized.uiViewContextAnchor;
views[name] = config;
});
return views;
}
// for ng 1.2 style, process the scope: { input: "=foo" } object
const scopeBindings = bindingsObj => Object.keys(bindingsObj || {})
.map(key => [key, /^[=<](.*)/.exec(bindingsObj[key])])
.filter(tuple => isDefined(tuple[1]))
.map(tuple => tuple[1][1] || tuple[0]);
// for ng 1.3+ bindToController or 1.5 component style, process a $$bindings object
const bindToCtrlBindings = bindingsObj => Object.keys(bindingsObj || {})
.filter(key => !!/[=<]/.exec(bindingsObj[key].mode))
.map(key => bindingsObj[key].attrName);
// Given a directive definition, find its object input attributes
// Use different properties, depending on the type of directive (component, bindToController, normal)
const getBindings = def => {
if (isObject(def.bindToController)) return scopeBindings(def.bindToController);
if (def.$$bindings && def.$$bindings.bindToController) return bindToCtrlBindings(def.$$bindings.bindToController);
if (def.$$isolateBindings) return bindToCtrlBindings(def.$$isolateBindings);
return <any> scopeBindings(def.scope);
};
// Gets all the directive(s)' inputs ('=' and '<')
function getComponentInputs($injector, name) {
let cmpDefs = $injector.get(name + "Directive"); // could be multiple
if (!cmpDefs || !cmpDefs.length) throw new Error(`Unable to find component named '${name}'`);
return cmpDefs.map(getBindings).reduce(unnestR, []);
}
export class Ng1ViewConfig implements ViewConfig {
loaded: boolean = false;
controller: Function;
template: string;
locals: any; // TODO: delete me
constructor(public node: Node, public viewDecl: Ng1ViewDeclaration) { }
load() {
let $q = services.$q;
if (!this.hasTemplate())
throw new Error(`No template configuration specified for '${this.viewDecl.$uiViewName}@${this.viewDecl.$uiViewContextAnchor}'`);
let injector = this.node.resolveContext;
let params = this.node.paramValues;
let promises: any = {
template: $q.when(this.getTemplate(params, new TemplateFactory(), injector)),
controller: $q.when(this.getController(injector))
};
return $q.all(promises).then((results) => {
trace.traceViewServiceEvent("Loaded", this);
this.controller = results.controller;
this.template = results.template;
});
}
/**
* Checks a view configuration to ensure that it specifies a template.
*
* @return {boolean} Returns `true` if the configuration contains a valid template, otherwise `false`.
*/
hasTemplate() {
return !!(this.viewDecl.template || this.viewDecl.templateUrl || this.viewDecl.templateProvider);
}
getTemplate(params, $factory, injector: ResolveContext) {
return $factory.fromConfig(this.viewDecl, params, injector.invokeLater.bind(injector));
}
/**
* Gets the controller for a view configuration.
*
* @returns {Function|Promise.<Function>} Returns a controller, or a promise that resolves to a controller.
*/
getController(injector: ResolveContext) {
//* @param {Object} locals A context object from transition.context() to invoke a function in the correct context
let provider = this.viewDecl.controllerProvider;
return isInjectable(provider) ? injector.invokeLater(provider, {}) : this.viewDecl.controller;
}
}