feat($compile): allow $watchCollection to be used in bi-directional bindings

an asterisk can be specified in the binding definition to use $watchCollection instead of $watch.
e.g. scope: { prop: '=*' }

Closes #9725
This commit is contained in:
Gabriel Monteagudo
2014-10-21 13:33:12 -02:00
committed by Caitlin Potter
parent 9ad6c77568
commit 40bbc98178
2 changed files with 63 additions and 6 deletions

View File

@@ -164,7 +164,9 @@
* value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
* in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
* scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
* can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional.
* can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If
* you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use
* `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional).
*
* * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
* If no `attr` name is specified then the attribute name is assumed to be the same as the
@@ -691,7 +693,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
function parseIsolateBindings(scope, directiveName) {
var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
var bindings = {};
@@ -706,9 +708,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
bindings[scopeName] = {
attrName: match[3] || scopeName,
mode: match[1],
optional: match[2] === '?'
mode: match[1][0],
collection: match[2] === '*',
optional: match[3] === '?',
attrName: match[4] || scopeName
};
});
@@ -1890,7 +1893,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return lastValue = parentValue;
};
parentValueWatch.$stateful = true;
var unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
var unwatch;
if (definition.collection) {
unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
} else {
unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
}
isolateScope.$on('$destroy', unwatch);
break;

View File

@@ -3065,6 +3065,8 @@ describe('$compile', function() {
optref: '=?',
optrefAlias: '=? optref',
optreference: '=?',
colref: '=*',
colrefAlias: '=* colref',
expr: '&',
exprAlias: '&expr'
},
@@ -3562,6 +3564,53 @@ describe('$compile', function() {
});
describe('collection object reference', function () {
it('should update isolate scope when origin scope changes', inject(function () {
$rootScope.collection = [{
name: 'Gabriel',
value: 18
}, {
name: 'Tony',
value: 91
}];
$rootScope.query = "";
$rootScope.$apply();
compile('<div><span my-component colref="collection | filter:query">');
expect(componentScope.colref).toEqual($rootScope.collection);
expect(componentScope.colrefAlias).toEqual(componentScope.colref);
$rootScope.query = "Gab";
$rootScope.$apply();
expect(componentScope.colref).toEqual([$rootScope.collection[0]]);
expect(componentScope.colrefAlias).toEqual([$rootScope.collection[0]]);
}));
it('should update origin scope when isolate scope changes', inject(function () {
$rootScope.collection = [{
name: 'Gabriel',
value: 18
}, {
name: 'Tony',
value: 91
}];
compile('<div><span my-component colref="collection">');
var newItem = {
name: 'Pablo',
value: 10
};
componentScope.colref.push(newItem);
componentScope.$apply();
expect($rootScope.collection[2]).toEqual(newItem);
}));
});
describe('executable expression', function() {
it('should allow expression execution with locals', inject(function() {
compile('<div><span my-component expr="count = count + offset">');