Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit fad5869

Browse files
committed
feat($compile): support expansion of special ngBindon attributes
In order to reduce some of the verbosity associated with conforming to the ideas behind unidirectional data-flow, we can introduce a special attribute syntax that will be automatically expanded in order to simulate two-way data binding. For example, `<my-component ng-bindon-foo="bar">` will be expanded into `<my-component foo="bar" foo-change="bar=$event">` Fixes #15455
1 parent f5d2bf3 commit fad5869

File tree

3 files changed

+61
-6
lines changed

3 files changed

+61
-6
lines changed

Diff for: docs/content/guide/component.ngdoc

+15
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,21 @@ components should follow a few simple conventions:
147147
});
148148
}
149149
```
150+
- A two-way binding can be simulated by using both a one-way binding as well as an output event.
151+
```js
152+
bindings: {
153+
hero: '<',
154+
heroChange: '&'
155+
}
156+
```
157+
```html
158+
<hero-detail hero="$ctrl.hero" hero-change="$ctrl.hero = $event"></hero-detail>
159+
```
160+
- Since that can be rather verbose, especially with repeated properties, we provide syntactic sugar for that pattern
161+
via `ng-bindon-` attributes which expand to match the example above.
162+
```html
163+
<hero-detail ng-bindon-hero="$ctrl.hero"></hero-detail>
164+
```
150165

151166
- **Components have a well-defined lifecycle**
152167
Each component can implement "lifecycle hooks". These are methods that will be called at certain points in the life

Diff for: src/ng/compile.js

+16-6
Original file line numberDiff line numberDiff line change
@@ -1803,7 +1803,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
18031803
: function denormalizeTemplate(template) {
18041804
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
18051805
},
1806-
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
1806+
NG_SPECIAL_ATTR = /^ng(Attr|Bindon)[A-Z]/;
18071807
var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;
18081808

18091809
compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
@@ -2139,8 +2139,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
21392139
directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective);
21402140

21412141
// iterate over the attributes
2142-
for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
2143-
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
2142+
for (var attr, name, nName, ngAttrName, value, isNgAttr, isSpecialAttr, isNgBindon,
2143+
nAttrs = node.attributes, j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
21442144
var attrStartName = false;
21452145
var attrEndName = false;
21462146

@@ -2150,10 +2150,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
21502150

21512151
// support ngAttr attribute binding
21522152
ngAttrName = directiveNormalize(name);
2153-
isNgAttr = NG_ATTR_BINDING.test(ngAttrName);
2154-
if (isNgAttr) {
2153+
2154+
isSpecialAttr = NG_SPECIAL_ATTR.exec(ngAttrName);
2155+
isNgAttr = isSpecialAttr && isSpecialAttr[1] === 'Attr';
2156+
isNgBindon = isSpecialAttr && isSpecialAttr[1] === 'Bindon';
2157+
2158+
if (isSpecialAttr) {
21552159
name = name.replace(PREFIX_REGEXP, '')
2156-
.substr(8).replace(/_(.)/g, function(match, letter) {
2160+
.substr(isNgAttr ? 8 : 10).replace(/_(.)/g, function(match, letter) {
21572161
return letter.toUpperCase();
21582162
});
21592163
}
@@ -2173,6 +2177,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
21732177
attrs[nName] = true; // presence means true
21742178
}
21752179
}
2180+
2181+
if (isNgBindon) {
2182+
attrs[nName] = value;
2183+
attrs[nName + 'Change'] = value + '=$event';
2184+
}
2185+
21762186
addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
21772187
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
21782188
attrEndName);

Diff for: test/ng/compileSpec.js

+30
Original file line numberDiff line numberDiff line change
@@ -11441,6 +11441,36 @@ describe('$compile', function() {
1144111441
});
1144211442
});
1144311443

11444+
describe('ngBindon attributes', function() {
11445+
it('should expand `ng-bindon-foo` to both an input and an output expression', function() {
11446+
module(function($compileProvider) {
11447+
$compileProvider.component('test', {
11448+
bindings: {
11449+
foo: '<',
11450+
fooChange: '&'
11451+
}
11452+
});
11453+
});
11454+
11455+
inject(function($compile, $rootScope) {
11456+
$rootScope.bar = 0;
11457+
11458+
element = $compile('<test ng-bindon-foo="bar"></test>')($rootScope);
11459+
var testController = element.controller('test');
11460+
$rootScope.$digest();
11461+
11462+
expect(testController.foo).toBe(0);
11463+
$rootScope.$apply('bar=1');
11464+
expect(testController.foo).toBe(1);
11465+
$rootScope.$apply(function() {
11466+
testController.fooChange({ $event: 2 });
11467+
});
11468+
11469+
expect($rootScope.bar).toBe(2);
11470+
expect(testController.foo).toBe(2);
11471+
});
11472+
});
11473+
});
1144411474

1144511475
describe('when an attribute has an underscore-separated name', function() {
1144611476

0 commit comments

Comments
 (0)