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

Commit ac899d0

Browse files
lrlopezIgorMinar
authored andcommitted
feat($compile): '=?' makes '=' binding optional
If you bind using '=' to a non-existant parent property, the compiler will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception, which is right because the model doesn't exist. This enhancement allow to specify that a binding is optional so it won't complain if the parent property is not defined. In order to mantain backward compability, the new behaviour must be specified using '=?' instead of '='. The local property will be undefined is these cases. Closes #909 Closes #1435
1 parent 30162b7 commit ac899d0

File tree

3 files changed

+39
-3
lines changed

3 files changed

+39
-3
lines changed

Diff for: docs/content/guide/directive.ngdoc

+3-1
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,9 @@ compiler}. The attributes are:
336336
Given `<widget my-attr="parentModel">` and widget definition of
337337
`scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the
338338
value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected
339-
in `localModel` and any changes in `localModel` will reflect in `parentModel`.
339+
in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent
340+
scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You
341+
can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional.
340342

341343
* `&` or `&attr` - provides a way to execute an expression in the context of the parent scope.
342344
If no `attr` name is specified then the attribute name is assumed to be the same as the

Diff for: src/ng/compile.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -768,13 +768,14 @@ function $CompileProvider($provide) {
768768
$element = attrs.$$element;
769769

770770
if (newIsolateScopeDirective) {
771-
var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/;
771+
var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
772772

773773
var parentScope = scope.$parent || scope;
774774

775775
forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
776776
var match = definiton.match(LOCAL_REGEXP) || [],
777-
attrName = match[2]|| scopeName,
777+
attrName = match[3] || scopeName,
778+
optional = (match[2] == '?'),
778779
mode = match[1], // @, =, or &
779780
lastValue,
780781
parentGet, parentSet;
@@ -796,6 +797,9 @@ function $CompileProvider($provide) {
796797
}
797798

798799
case '=': {
800+
if (optional && !attrs[attrName]) {
801+
return;
802+
}
799803
parentGet = $parse(attrs[attrName]);
800804
parentSet = parentGet.assign || function() {
801805
// reset the change, or we will throw this exception on every $digest

Diff for: test/ng/compileSpec.js

+30
Original file line numberDiff line numberDiff line change
@@ -1857,6 +1857,9 @@ describe('$compile', function() {
18571857
ref: '=',
18581858
refAlias: '= ref',
18591859
reference: '=',
1860+
optref: '=?',
1861+
optrefAlias: '=? optref',
1862+
optreference: '=?',
18601863
expr: '&',
18611864
exprAlias: '&expr'
18621865
},
@@ -1993,6 +1996,33 @@ describe('$compile', function() {
19931996
});
19941997

19951998

1999+
describe('optional object reference', function() {
2000+
it('should update local when origin changes', inject(function() {
2001+
compile('<div><span my-component optref="name">');
2002+
expect(componentScope.optRef).toBe(undefined);
2003+
expect(componentScope.optRefAlias).toBe(componentScope.optRef);
2004+
2005+
$rootScope.name = 'misko';
2006+
$rootScope.$apply();
2007+
expect(componentScope.optref).toBe($rootScope.name);
2008+
expect(componentScope.optrefAlias).toBe($rootScope.name);
2009+
2010+
$rootScope.name = {};
2011+
$rootScope.$apply();
2012+
expect(componentScope.optref).toBe($rootScope.name);
2013+
expect(componentScope.optrefAlias).toBe($rootScope.name);
2014+
}));
2015+
2016+
it('should not throw exception when reference does not exist', inject(function() {
2017+
compile('<div><span my-component>');
2018+
2019+
expect(componentScope.optref).toBe(undefined);
2020+
expect(componentScope.optrefAlias).toBe(undefined);
2021+
expect(componentScope.optreference).toBe(undefined);
2022+
}));
2023+
});
2024+
2025+
19962026
describe('executable expression', function() {
19972027
it('should allow expression execution with locals', inject(function() {
19982028
compile('<div><span my-component expr="count = count + offset">');

0 commit comments

Comments
 (0)