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

Commit

Permalink
feat($compile): add one-way collection bindings
Browse files Browse the repository at this point in the history
Closes #14039
Closes #16553
Closes  #15874
  • Loading branch information
jbedard authored and Narretz committed May 17, 2018
1 parent f6e189d commit f9d1ca2
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 2 deletions.
9 changes: 7 additions & 2 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,11 @@
* One-way binding is useful if you do not plan to propagate changes to your isolated scope bindings
* back to the parent. However, it does not make this completely impossible.
*
* By default, the {@link ng.$rootScope.Scope#$watch `$watch`}
* method is used for tracking changes, and the equality check is based on object identity.
* It's also possible to watch the evaluated value shallowly with
* {@link ng.$rootScope.Scope#$watchCollection `$watchCollection`}: use `<*` or `<*attr`
*
* * `&` 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 local name.
* Given `<my-component my-attr="count = count + value">` and the isolate scope definition `scope: {
Expand Down Expand Up @@ -1068,7 +1073,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var bindingCache = createMap();

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

var bindings = createMap();

Expand Down Expand Up @@ -3615,7 +3620,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var initialValue = destination[scopeName] = parentGet(scope);
initialChanges[scopeName] = new SimpleChange(_UNINITIALIZED_VALUE, destination[scopeName]);

removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newValue, oldValue) {
removeWatch = scope[definition.collection ? '$watchCollection' : '$watch'](parentGet, function parentValueWatchAction(newValue, oldValue) {
if (oldValue === newValue) {
if (oldValue === initialValue || (isLiteral && equals(oldValue, initialValue))) {
return;
Expand Down
109 changes: 109 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5148,6 +5148,9 @@ describe('$compile', function() {
owOptref: '<?',
owOptrefAlias: '<? owOptref',
$owOptrefAlias: '<? $owOptref$',
owColref: '<*',
owColrefAlias: '<* owColref',
$owColrefAlias: '<* $owColref$',
expr: '&',
optExpr: '&?',
exprAlias: '&expr',
Expand Down Expand Up @@ -6327,6 +6330,112 @@ describe('$compile', function() {
});
});

describe('one-way collection bindings', 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 ow-colref="collection | filter:query" $ow-colref$="collection | filter:query">');

expect(componentScope.owColref).toEqual($rootScope.collection);
expect(componentScope.owColrefAlias).toEqual(componentScope.owColref);
expect(componentScope.$owColrefAlias).toEqual(componentScope.owColref);

$rootScope.query = 'Gab';
$rootScope.$apply();

expect(componentScope.owColref).toEqual([$rootScope.collection[0]]);
expect(componentScope.owColrefAlias).toEqual([$rootScope.collection[0]]);
expect(componentScope.$owColrefAlias).toEqual([$rootScope.collection[0]]);
}));

it('should not update isolate scope when deep state within origin scope changes', inject(function() {
$rootScope.collection = [{
name: 'Gabriel',
value: 18
}, {
name: 'Tony',
value: 91
}];
$rootScope.$apply();

compile('<div><span my-component ow-colref="collection" $ow-colref$="collection">');

expect(componentScope.owColref).toEqual($rootScope.collection);
expect(componentScope.owColrefAlias).toEqual(componentScope.owColref);
expect(componentScope.$owColrefAlias).toEqual(componentScope.owColref);

componentScope.owColref = componentScope.owColrefAlias = componentScope.$owColrefAlias = undefined;
$rootScope.collection[0].name = 'Joe';
$rootScope.$apply();

expect(componentScope.owColref).toBeUndefined();
expect(componentScope.owColrefAlias).toBeUndefined();
expect(componentScope.$owColrefAlias).toBeUndefined();
}));

it('should update isolate scope when origin scope changes', inject(function() {
$rootScope.gab = {
name: 'Gabriel',
value: 18
};
$rootScope.tony = {
name: 'Tony',
value: 91
};
$rootScope.query = '';
$rootScope.$apply();

compile('<div><span my-component ow-colref="[gab, tony] | filter:query" $ow-colref$="[gab, tony] | filter:query">');

expect(componentScope.owColref).toEqual([$rootScope.gab, $rootScope.tony]);
expect(componentScope.owColrefAlias).toEqual([$rootScope.gab, $rootScope.tony]);
expect(componentScope.$owColrefAlias).toEqual([$rootScope.gab, $rootScope.tony]);

$rootScope.query = 'Gab';
$rootScope.$apply();

expect(componentScope.owColref).toEqual([$rootScope.gab]);
expect(componentScope.owColrefAlias).toEqual([$rootScope.gab]);
expect(componentScope.$owColrefAlias).toEqual([$rootScope.gab]);
}));

it('should update isolate scope when origin literal object content changes', inject(function() {
$rootScope.gab = {
name: 'Gabriel',
value: 18
};
$rootScope.tony = {
name: 'Tony',
value: 91
};
$rootScope.$apply();

compile('<div><span my-component ow-colref="[gab, tony]" $ow-colref$="[gab, tony]">');

expect(componentScope.owColref).toEqual([$rootScope.gab, $rootScope.tony]);
expect(componentScope.owColrefAlias).toEqual([$rootScope.gab, $rootScope.tony]);
expect(componentScope.$owColrefAlias).toEqual([$rootScope.gab, $rootScope.tony]);

$rootScope.tony = {
name: 'Bob',
value: 42
};
$rootScope.$apply();

expect(componentScope.owColref).toEqual([$rootScope.gab, $rootScope.tony]);
expect(componentScope.owColrefAlias).toEqual([$rootScope.gab, $rootScope.tony]);
expect(componentScope.$owColrefAlias).toEqual([$rootScope.gab, $rootScope.tony]);
}));
});

describe('executable expression', function() {
it('should allow expression execution with locals', inject(function() {
compile('<div><span my-component expr="count = count + offset" $expr$="count = count + offset">');
Expand Down

0 comments on commit f9d1ca2

Please sign in to comment.