Skip to content

Commit 71b3393

Browse files
fix(ng1.component): Allow route-to-component "@" and optional bindings
Closes #2708
1 parent 9842fb7 commit 71b3393

File tree

3 files changed

+67
-16
lines changed

3 files changed

+67
-16
lines changed

src/common/strings.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ export function padString(length: number, str: string) {
3535
return str;
3636
}
3737

38-
export const kebobString = (camelCase: string) => camelCase.replace(/([A-Z])/g, $1 => "-"+$1.toLowerCase());
38+
export function kebobString(camelCase: string) {
39+
return camelCase
40+
.replace(/^([A-Z])/, $1 => $1.toLowerCase()) // replace first char
41+
.replace(/([A-Z])/g, $1 => "-" + $1.toLowerCase()); // replace rest
42+
}
3943

4044
function _toJson(obj) {
4145
return JSON.stringify(obj);

src/ng1/viewsBuilder.ts

+16-15
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,19 @@ export function ng1ViewsBuilder(state: State) {
4545
throw new Error(`Cannot combine: ${compKeys.join("|")} with: ${nonCompKeys.join("|")} in stateview: 'name@${state.name}'`);
4646
}
4747

48-
// Dynamically build a template like "<component-name input1='$resolve.foo'></component-name>"
48+
// Dynamically build a template like "<component-name input1='::$resolve.foo'></component-name>"
4949
config.templateProvider = ['$injector', function($injector) {
5050
const resolveFor = key => config.bindings && config.bindings[key] || key;
5151
const prefix = angular.version.minor >= 3 ? "::" : "";
52-
let attrs = getComponentInputs($injector, config.component)
53-
.map(key => `${kebobString(key)}='${prefix}$resolve.${resolveFor(key)}'`).join(" ");
52+
const attributeTpl = input => {
53+
var attrName = kebobString(input.name);
54+
var resolveName = resolveFor(input.name);
55+
if (input.type === '@')
56+
return `${attrName}='{{${prefix}$resolve.${resolveName}}}'`;
57+
return `${attrName}='${prefix}$resolve.${resolveName}'`;
58+
};
59+
60+
let attrs = getComponentInputs($injector, config.component).map(attributeTpl).join(" ");
5461
let kebobName = kebobString(config.component);
5562
return `<${kebobName} ${attrs}></${kebobName}>`;
5663
}];
@@ -70,27 +77,21 @@ export function ng1ViewsBuilder(state: State) {
7077
return views;
7178
}
7279

73-
// for ng 1.2 style, process the scope: { input: "=foo" } object
80+
// for ng 1.2 style, process the scope: { input: "=foo" }
81+
// for ng 1.3 through ng 1.5, process the component's bindToController: { input: "=foo" } object
7482
const scopeBindings = bindingsObj => Object.keys(bindingsObj || {})
75-
.map(key => [key, /^[=<](.*)/.exec(bindingsObj[key])])
76-
.filter(tuple => isDefined(tuple[1]))
77-
.map(tuple => tuple[1][1] || tuple[0]);
78-
79-
// for ng 1.3+ bindToController or 1.5 component style, process a $$bindings object
80-
const bindToCtrlBindings = bindingsObj => Object.keys(bindingsObj || {})
81-
.filter(key => !!/[=<]/.exec(bindingsObj[key].mode))
82-
.map(key => bindingsObj[key].attrName);
83+
.map(key => [key, /^([=<@])[?]?(.*)/.exec(bindingsObj[key])]) // [ 'input', [ '=foo', '=', 'foo' ] ]
84+
.filter(tuple => isDefined(tuple) && isDefined(tuple[1])) // skip malformed values
85+
.map(tuple => ({ name: tuple[1][2] || tuple[0], type: tuple[1][1] }));// { name: ('foo' || 'input'), type: '=' }
8386

8487
// Given a directive definition, find its object input attributes
8588
// Use different properties, depending on the type of directive (component, bindToController, normal)
8689
const getBindings = def => {
8790
if (isObject(def.bindToController)) return scopeBindings(def.bindToController);
88-
if (def.$$bindings && def.$$bindings.bindToController) return bindToCtrlBindings(def.$$bindings.bindToController);
89-
if (def.$$isolateBindings) return bindToCtrlBindings(def.$$isolateBindings);
9091
return <any> scopeBindings(def.scope);
9192
};
9293

93-
// Gets all the directive(s)' inputs ('=' and '<')
94+
// Gets all the directive(s)' inputs ('@', '=', and '<')
9495
function getComponentInputs($injector, name) {
9596
let cmpDefs = $injector.get(name + "Directive"); // could be multiple
9697
if (!cmpDefs || !cmpDefs.length) throw new Error(`Unable to find component named '${name}'`);

test/ng1/viewDirectiveSpec.js

+46
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,16 @@ describe('angular 1.5+ style .component()', function() {
804804
bindings: { status: '<' },
805805
template: '#{{ $ctrl.status }}#'
806806
});
807+
808+
app.component('bindingTypes', {
809+
bindings: { oneway: '<oneway', twoway: '=', attribute: '@attr' },
810+
template: '-{{ $ctrl.oneway }},{{ $ctrl.twoway }},{{ $ctrl.attribute }}-'
811+
});
812+
813+
app.component('optionalBindingTypes', {
814+
bindings: { oneway: '<?oneway', twoway: '=?', attribute: '@?attr' },
815+
template: '-{{ $ctrl.oneway }},{{ $ctrl.twoway }},{{ $ctrl.attribute }}-'
816+
});
807817
}
808818
}));
809819

@@ -993,6 +1003,42 @@ describe('angular 1.5+ style .component()', function() {
9931003

9941004
expect(log).toBe('onInit;');
9951005
});
1006+
1007+
it('should supply resolve data to "<", "=", "@" bindings', function () {
1008+
$stateProvider.state('bindingtypes', {
1009+
component: 'bindingTypes',
1010+
resolve: {
1011+
oneway: function () { return "ONEWAY"; },
1012+
twoway: function () { return "TWOWAY"; },
1013+
attribute: function () { return "ATTRIBUTE"; }
1014+
},
1015+
bindings: { attr: 'attribute' }
1016+
});
1017+
1018+
var $state = svcs.$state, $httpBackend = svcs.$httpBackend, $q = svcs.$q;
1019+
1020+
$state.transitionTo('bindingtypes'); $q.flush();
1021+
1022+
expect(el.text()).toBe('-ONEWAY,TWOWAY,ATTRIBUTE-');
1023+
});
1024+
1025+
it('should supply resolve data to optional "<?", "=?", "@?" bindings', function () {
1026+
$stateProvider.state('optionalbindingtypes', {
1027+
component: 'optionalBindingTypes',
1028+
resolve: {
1029+
oneway: function () { return "ONEWAY"; },
1030+
twoway: function () { return "TWOWAY"; },
1031+
attribute: function () { return "ATTRIBUTE"; }
1032+
},
1033+
bindings: { attr: 'attribute' }
1034+
});
1035+
1036+
var $state = svcs.$state, $httpBackend = svcs.$httpBackend, $q = svcs.$q;
1037+
1038+
$state.transitionTo('optionalbindingtypes'); $q.flush();
1039+
1040+
expect(el.text()).toBe('-ONEWAY,TWOWAY,ATTRIBUTE-');
1041+
});
9961042
}
9971043
});
9981044

0 commit comments

Comments
 (0)