Skip to content

Commit 6922ffb

Browse files
committed
fix($compile): throw when non-optional '&' binding is used but not specified
Also, do not set up an expression in scope if the '&' binding is optional. BREAKING CHANGE: Previously, '&' expressions would always set up a function in the isolate scope. Now, if the binding is marked as optional and the attribute is not specified, no function will be added to the isolate scope. Finally, if the '&' expression is not optional, and the attribute is not specified, then rather than the function being essentially a NOOP, it will instead throw an error indicating to the programmer that a required attribute was not specified. Closes angular#6404
1 parent df8d950 commit 6922ffb

File tree

3 files changed

+36
-0
lines changed

3 files changed

+36
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@ngdoc error
2+
@name $compile:reqattr
3+
@fullName Required Attribute Not Present
4+
@description
5+
6+
If a directive requires an attribute to be present, by specifying a non-optional isolate scope binding for that attribute, and the attribute
7+
not present, this error is thrown.
8+
9+
To prevent this error from being thrown, ensure that required attributes are all specified when using a custom component.

src/ng/compile.js

+9
Original file line numberDiff line numberDiff line change
@@ -1744,6 +1744,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
17441744
break;
17451745

17461746
case '&':
1747+
if (isUndefined(attrs[attrName])) {
1748+
if (optional) break;
1749+
isolateBindingContext[scopeName] = function unspecifiedExpressionAttr(locals) {
1750+
throw $compileMinErr('reqattr',
1751+
"Directive '{0}' requires attribute '{1}' which is not specified.",
1752+
newIsolateScopeDirective.name, attrName);
1753+
};
1754+
break;
1755+
}
17471756
parentGet = $parse(attrs[attrName]);
17481757
isolateBindingContext[scopeName] = function(locals) {
17491758
return parentGet(scope, locals);

test/ng/compileSpec.js

+18
Original file line numberDiff line numberDiff line change
@@ -3013,6 +3013,7 @@ describe('$compile', function() {
30133013
optrefAlias: '=? optref',
30143014
optreference: '=?',
30153015
expr: '&',
3016+
optExpr: '&?',
30163017
exprAlias: '&expr'
30173018
},
30183019
link: function(scope) {
@@ -3124,6 +3125,23 @@ describe('$compile', function() {
31243125
}));
31253126

31263127

3128+
it('should throw if non-optional expression is used and not specified', inject(function($compile) {
3129+
compile('<div my-component></div>');
3130+
var isolateScope = element.isolateScope();
3131+
expect(typeof isolateScope.expr).toBe('function');
3132+
expect(function() {
3133+
isolateScope.expr();
3134+
}).toThrowMinErr('$compile', 'reqattr', "Directive 'myComponent' requires attribute 'expr' which is not specified.");
3135+
}));
3136+
3137+
3138+
it('should not create function in isolate scope if optional expression is not specified', inject(function($compile) {
3139+
compile('<div my-component></div>');
3140+
var isolateScope = element.isolateScope();
3141+
expect(isolateScope.optExpr).toBeUndefined();
3142+
}));
3143+
3144+
31273145
describe('bind-once', function () {
31283146

31293147
function countWatches(scope) {

0 commit comments

Comments
 (0)