Skip to content

Commit 536c07e

Browse files
jbedardJason Bedard
authored and
Jason Bedard
committed
feat($compile): add one-way collection bindings
Fixes angular#14039 Fixes angular#15874 Closes angular#16553
1 parent fb2d3fa commit 536c07e

File tree

2 files changed

+116
-2
lines changed

2 files changed

+116
-2
lines changed

src/ng/compile.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,11 @@
339339
* One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings
340340
* back to the parent. However, it does not make this completely impossible.
341341
*
342+
* By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
343+
* method is used for tracking changes, and the equality check is based on object identity.
344+
* It's also possible to watch the evaluated value shallowly with
345+
* {@link ng.$rootScope.Scope#$watchCollection `$watchCollection`}: use `<*` or `<*attr`
346+
*
342347
* * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. If
343348
* no `attr` name is specified then the attribute name is assumed to be the same as the local name.
344349
* Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: {
@@ -1068,7 +1073,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
10681073
var bindingCache = createMap();
10691074

10701075
function parseIsolateBindings(scope, directiveName, isController) {
1071-
var LOCAL_REGEXP = /^([@&<]|=(\*?))(\??)\s*([\w$]*)$/;
1076+
var LOCAL_REGEXP = /^([@&]|[=<](\*?))(\??)\s*([\w$]*)$/;
10721077

10731078
var bindings = createMap();
10741079

@@ -3626,7 +3631,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
36263631
var initialValue = destination[scopeName] = parentGet(scope);
36273632
initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);
36283633

3629-
removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) {
3634+
removeWatch = scope[definition.collection ? "$watchCollection" : "$watch"](parentGet, function parentValueWatchAction(newValue, oldValue) {
36303635
if (oldValue === newValue) {
36313636
if (oldValue === initialValue || (isLiteral && equals(oldValue, initialValue))) {
36323637
return;

test/ng/compileSpec.js

+109
Original file line numberDiff line numberDiff line change
@@ -5148,6 +5148,9 @@ describe('$compile', function() {
51485148
owOptref: '<?',
51495149
owOptrefAlias: '<? owOptref',
51505150
$owOptrefAlias: '<? $owOptref$',
5151+
owColref: '<*',
5152+
owColrefAlias: '<* owColref',
5153+
$owColrefAlias: '<* $owColref$',
51515154
expr: '&',
51525155
optExpr: '&?',
51535156
exprAlias: '&expr',
@@ -6327,6 +6330,112 @@ describe('$compile', function() {
63276330
});
63286331
});
63296332

6333+
describe('one-way collection bindings', function() {
6334+
it('should update isolate scope when origin scope changes', inject(function() {
6335+
$rootScope.collection = [{
6336+
name: 'Gabriel',
6337+
value: 18
6338+
}, {
6339+
name: 'Tony',
6340+
value: 91
6341+
}];
6342+
$rootScope.query = '';
6343+
$rootScope.$apply();
6344+
6345+
compile('<div><span my-component ow-colref="collection | filter:query" $ow-colref$="collection | filter:query">');
6346+
6347+
expect(componentScope.owColref).toEqual($rootScope.collection);
6348+
expect(componentScope.owColrefAlias).toEqual(componentScope.owColref);
6349+
expect(componentScope.$owColrefAlias).toEqual(componentScope.owColref);
6350+
6351+
$rootScope.query = 'Gab';
6352+
$rootScope.$apply();
6353+
6354+
expect(componentScope.owColref).toEqual([$rootScope.collection[0]]);
6355+
expect(componentScope.owColrefAlias).toEqual([$rootScope.collection[0]]);
6356+
expect(componentScope.$owColrefAlias).toEqual([$rootScope.collection[0]]);
6357+
}));
6358+
6359+
it('should not update isolate scope when deep state within origin scope changes', inject(function() {
6360+
$rootScope.collection = [{
6361+
name: 'Gabriel',
6362+
value: 18
6363+
}, {
6364+
name: 'Tony',
6365+
value: 91
6366+
}];
6367+
$rootScope.$apply();
6368+
6369+
compile('<div><span my-component ow-colref="collection" $ow-colref$="collection">');
6370+
6371+
expect(componentScope.owColref).toEqual($rootScope.collection);
6372+
expect(componentScope.owColrefAlias).toEqual(componentScope.owColref);
6373+
expect(componentScope.$owColrefAlias).toEqual(componentScope.owColref);
6374+
6375+
componentScope.owColref = componentScope.owColrefAlias = componentScope.$owColrefAlias = undefined;
6376+
$rootScope.collection[0].name = 'Joe';
6377+
$rootScope.$apply();
6378+
6379+
expect(componentScope.owColref).toBeUndefined();
6380+
expect(componentScope.owColrefAlias).toBeUndefined();
6381+
expect(componentScope.$owColrefAlias).toBeUndefined();
6382+
}));
6383+
6384+
it('should update isolate scope when origin scope changes', inject(function() {
6385+
$rootScope.gab = {
6386+
name: 'Gabriel',
6387+
value: 18
6388+
};
6389+
$rootScope.tony = {
6390+
name: 'Tony',
6391+
value: 91
6392+
};
6393+
$rootScope.query = '';
6394+
$rootScope.$apply();
6395+
6396+
compile('<div><span my-component ow-colref="[gab, tony] | filter:query" $ow-colref$="[gab, tony] | filter:query">');
6397+
6398+
expect(componentScope.owColref).toEqual([$rootScope.gab, $rootScope.tony]);
6399+
expect(componentScope.owColrefAlias).toEqual([$rootScope.gab, $rootScope.tony]);
6400+
expect(componentScope.$owColrefAlias).toEqual([$rootScope.gab, $rootScope.tony]);
6401+
6402+
$rootScope.query = 'Gab';
6403+
$rootScope.$apply();
6404+
6405+
expect(componentScope.owColref).toEqual([$rootScope.gab]);
6406+
expect(componentScope.owColrefAlias).toEqual([$rootScope.gab]);
6407+
expect(componentScope.$owColrefAlias).toEqual([$rootScope.gab]);
6408+
}));
6409+
6410+
it('should update isolate scope when origin literal object content changes', inject(function() {
6411+
$rootScope.gab = {
6412+
name: 'Gabriel',
6413+
value: 18
6414+
};
6415+
$rootScope.tony = {
6416+
name: 'Tony',
6417+
value: 91
6418+
};
6419+
$rootScope.$apply();
6420+
6421+
compile('<div><span my-component ow-colref="[gab, tony]" $ow-colref$="[gab, tony]">');
6422+
6423+
expect(componentScope.owColref).toEqual([$rootScope.gab, $rootScope.tony]);
6424+
expect(componentScope.owColrefAlias).toEqual([$rootScope.gab, $rootScope.tony]);
6425+
expect(componentScope.$owColrefAlias).toEqual([$rootScope.gab, $rootScope.tony]);
6426+
6427+
$rootScope.tony = {
6428+
name: 'Bob',
6429+
value: 42
6430+
};
6431+
$rootScope.$apply();
6432+
6433+
expect(componentScope.owColref).toEqual([$rootScope.gab, $rootScope.tony]);
6434+
expect(componentScope.owColrefAlias).toEqual([$rootScope.gab, $rootScope.tony]);
6435+
expect(componentScope.$owColrefAlias).toEqual([$rootScope.gab, $rootScope.tony]);
6436+
}));
6437+
});
6438+
63306439
describe('executable expression', function() {
63316440
it('should allow expression execution with locals', inject(function() {
63326441
compile('<div><span my-component expr="count = count + offset" $expr$="count = count + offset">');

0 commit comments

Comments
 (0)