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

feat($compile): support expansion of special ngBindon attributes #15464

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/content/guide/component.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,21 @@ components should follow a few simple conventions:
});
}
```
- A two-way binding can be simulated by using both a one-way binding as well as an output event.
```js
bindings: {
hero: '<',
heroChange: '&'
}
```
```html
<hero-detail hero="$ctrl.hero" hero-change="$ctrl.hero = $event"></hero-detail>
```
- Since that can be rather verbose, especially with repeated properties, we provide syntactic sugar for that pattern
via `ng-bindon-` attributes which expand to match the example above.
```html
<hero-detail ng-bindon-hero="$ctrl.hero"></hero-detail>
```

- **Components have a well-defined lifecycle**
Each component can implement "lifecycle hooks". These are methods that will be called at certain points in the life
Expand Down
22 changes: 16 additions & 6 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1803,7 +1803,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
: function denormalizeTemplate(template) {
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
},
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
NG_SPECIAL_ATTR = /^ng(Attr|Bindon)[A-Z]/;
var MULTI_ELEMENT_DIR_RE = /^(.+)Start$/;

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

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

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

// support ngAttr attribute binding
ngAttrName = directiveNormalize(name);
isNgAttr = NG_ATTR_BINDING.test(ngAttrName);
if (isNgAttr) {

isSpecialAttr = NG_SPECIAL_ATTR.exec(ngAttrName);
isNgAttr = isSpecialAttr && isSpecialAttr[1] === 'Attr';
isNgBindon = isSpecialAttr && isSpecialAttr[1] === 'Bindon';

if (isSpecialAttr) {
name = name.replace(PREFIX_REGEXP, '')
.substr(8).replace(/_(.)/g, function(match, letter) {
.substr(isNgAttr ? 8 : 10).replace(/_(.)/g, function(match, letter) {
return letter.toUpperCase();
});
}
Expand All @@ -2173,6 +2177,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
attrs[nName] = true; // presence means true
}
}

if (isNgBindon) {
attrs[nName] = value;
attrs[nName + 'Change'] = value + '=$event';
}

addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
attrEndName);
Expand Down
30 changes: 30 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11441,6 +11441,36 @@ describe('$compile', function() {
});
});

describe('ngBindon attributes', function() {
it('should expand `ng-bindon-foo` to both an input and an output expression', function() {
module(function($compileProvider) {
$compileProvider.component('test', {
bindings: {
foo: '<',
fooChange: '&'
}
});
});

inject(function($compile, $rootScope) {
$rootScope.bar = 0;

element = $compile('<test ng-bindon-foo="bar"></test>')($rootScope);
var testController = element.controller('test');
$rootScope.$digest();

expect(testController.foo).toBe(0);
$rootScope.$apply('bar=1');
expect(testController.foo).toBe(1);
$rootScope.$apply(function() {
testController.fooChange({ $event: 2 });
});

expect($rootScope.bar).toBe(2);
expect(testController.foo).toBe(2);
});
});
});

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

Expand Down