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
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
@@ -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
22 changes: 16 additions & 6 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
@@ -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) {
@@ -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;

@@ -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();
});
}
@@ -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);
30 changes: 30 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
@@ -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() {