feat($compile): add strictComponentBindingsEnabled() method
Closes #16129
This commit is contained in:
committed by
Georgios Kalpakas
parent
01d6a47e91
commit
f1d01bbc74
@@ -0,0 +1,8 @@
|
||||
@ngdoc error
|
||||
@name $compile:missingattr
|
||||
@fullName Missing required attribute
|
||||
@description
|
||||
|
||||
This error may occur only when `$compileProvider.strictComponentBindingsEnabled` is set to `true`.
|
||||
Then all attributes mentioned in `bindings` without `?` must be set. If one or more aren't set,
|
||||
the first one will throw an error.
|
||||
@@ -1403,6 +1403,32 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
return debugInfoEnabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name $compileProvider#strictComponentBindingsEnabled
|
||||
*
|
||||
* @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided, otherwise just return the
|
||||
* current strictComponentBindingsEnabled state
|
||||
* @returns {*} current value if used as getter or itself (chaining) if used as setter
|
||||
*
|
||||
* @kind function
|
||||
*
|
||||
* @description
|
||||
* Call this method to enable/disable strict component bindings check. If enabled, the compiler will enforce that
|
||||
* for all bindings of a component that are not set as optional with `?`, an attribute needs to be provided
|
||||
* on the component's HTML tag.
|
||||
*
|
||||
* The default value is false.
|
||||
*/
|
||||
var strictComponentBindingsEnabled = false;
|
||||
this.strictComponentBindingsEnabled = function(enabled) {
|
||||
if (isDefined(enabled)) {
|
||||
strictComponentBindingsEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
return strictComponentBindingsEnabled;
|
||||
};
|
||||
|
||||
var TTL = 10;
|
||||
/**
|
||||
* @ngdoc method
|
||||
@@ -3413,12 +3439,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
}
|
||||
|
||||
function strictBindingsCheck(attrName, directiveName) {
|
||||
if (strictComponentBindingsEnabled) {
|
||||
throw $compileMinErr('missingattr',
|
||||
'Attribute \'{0}\' of \'{1}\' is non-optional and must be set!',
|
||||
attrName, directiveName);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up $watches for isolate scope and controller bindings.
|
||||
function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
|
||||
var removeWatchCollection = [];
|
||||
var initialChanges = {};
|
||||
var changes;
|
||||
|
||||
forEach(bindings, function initializeBinding(definition, scopeName) {
|
||||
var attrName = definition.attrName,
|
||||
optional = definition.optional,
|
||||
@@ -3430,7 +3464,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
case '@':
|
||||
if (!optional && !hasOwnProperty.call(attrs, attrName)) {
|
||||
strictBindingsCheck(attrName, directive.name);
|
||||
destination[scopeName] = attrs[attrName] = undefined;
|
||||
|
||||
}
|
||||
removeWatch = attrs.$observe(attrName, function(value) {
|
||||
if (isString(value) || isBoolean(value)) {
|
||||
@@ -3457,6 +3493,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
case '=':
|
||||
if (!hasOwnProperty.call(attrs, attrName)) {
|
||||
if (optional) break;
|
||||
strictBindingsCheck(attrName, directive.name);
|
||||
attrs[attrName] = undefined;
|
||||
}
|
||||
if (optional && !attrs[attrName]) break;
|
||||
@@ -3501,6 +3538,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
case '<':
|
||||
if (!hasOwnProperty.call(attrs, attrName)) {
|
||||
if (optional) break;
|
||||
strictBindingsCheck(attrName, directive.name);
|
||||
attrs[attrName] = undefined;
|
||||
}
|
||||
if (optional && !attrs[attrName]) break;
|
||||
@@ -3526,6 +3564,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
break;
|
||||
|
||||
case '&':
|
||||
if (!optional && !hasOwnProperty.call(attrs, attrName)) {
|
||||
strictBindingsCheck(attrName, directive.name);
|
||||
}
|
||||
// Don't assign Object.prototype method to scope
|
||||
parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
|
||||
|
||||
|
||||
@@ -169,6 +169,15 @@ describe('$compile', function() {
|
||||
inject();
|
||||
});
|
||||
|
||||
it('should allow strictComponentBindingsEnabled to be configured', function() {
|
||||
module(function($compileProvider) {
|
||||
expect($compileProvider.strictComponentBindingsEnabled()).toBe(false); // the default
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
expect($compileProvider.strictComponentBindingsEnabled()).toBe(true);
|
||||
});
|
||||
inject();
|
||||
});
|
||||
|
||||
it('should allow onChangesTtl to be configured', function() {
|
||||
module(function($compileProvider) {
|
||||
expect($compileProvider.onChangesTtl()).toBe(10); // the default
|
||||
@@ -2546,6 +2555,16 @@ describe('$compile', function() {
|
||||
template: '<span></span>'
|
||||
};
|
||||
});
|
||||
directive('prototypeMethodNameAsScopeVarD', function() {
|
||||
return {
|
||||
scope: {
|
||||
'constructor': '<?',
|
||||
'valueOf': '<'
|
||||
},
|
||||
restrict: 'AE',
|
||||
template: '<span></span>'
|
||||
};
|
||||
});
|
||||
directive('watchAsScopeVar', function() {
|
||||
return {
|
||||
scope: {
|
||||
@@ -2854,6 +2873,57 @@ describe('$compile', function() {
|
||||
})
|
||||
);
|
||||
|
||||
it('should throw an error for undefined non-optional "=" bindings when ' +
|
||||
'strictComponentBindingsEnabled is true', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
});
|
||||
inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
element = $compile(
|
||||
'<div prototype-method-name-as-scope-var-a></div>'
|
||||
)($rootScope);
|
||||
};
|
||||
expect(func).toThrowMinErr('$compile',
|
||||
'missingattr',
|
||||
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
|
||||
'ScopeVarA\' is non-optional and must be set!');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error for set non-optional "=" bindings when ' +
|
||||
'strictComponentBindingsEnabled is true', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
});
|
||||
inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
element = $compile(
|
||||
'<div prototype-method-name-as-scope-var-a constructor="constructor" value-of="valueOf"></div>'
|
||||
)($rootScope);
|
||||
};
|
||||
expect(func).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error for undefined optional "=" bindings when ' +
|
||||
'strictComponentBindingsEnabled is true', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
});
|
||||
inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
element = $compile(
|
||||
'<div prototype-method-name-as-scope-var-a value-of="valueOf"></div>'
|
||||
)($rootScope);
|
||||
};
|
||||
expect(func).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle "@" bindings with same method names in Object.prototype correctly when not present', inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
@@ -2891,6 +2961,57 @@ describe('$compile', function() {
|
||||
})
|
||||
);
|
||||
|
||||
it('should throw an error for undefined non-optional "@" bindings when ' +
|
||||
'strictComponentBindingsEnabled is true', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
});
|
||||
inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
element = $compile(
|
||||
'<div prototype-method-name-as-scope-var-b></div>'
|
||||
)($rootScope);
|
||||
};
|
||||
expect(func).toThrowMinErr('$compile',
|
||||
'missingattr',
|
||||
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
|
||||
'ScopeVarB\' is non-optional and must be set!');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error for set non-optional "@" bindings when ' +
|
||||
'strictComponentBindingsEnabled is true', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
});
|
||||
inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
element = $compile(
|
||||
'<div prototype-method-name-as-scope-var-b constructor="constructor" value-of="valueOf"></div>'
|
||||
)($rootScope);
|
||||
};
|
||||
expect(func).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error for undefined optional "@" bindings when ' +
|
||||
'strictComponentBindingsEnabled is true', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
});
|
||||
inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
element = $compile(
|
||||
'<div prototype-method-name-as-scope-var-b value-of="valueOf"></div>'
|
||||
)($rootScope);
|
||||
};
|
||||
expect(func).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle "&" bindings with same method names in Object.prototype correctly when not present', inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
@@ -2923,6 +3044,108 @@ describe('$compile', function() {
|
||||
})
|
||||
);
|
||||
|
||||
it('should throw an error for undefined non-optional "&" bindings when ' +
|
||||
'strictComponentBindingsEnabled is true', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
});
|
||||
inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
element = $compile(
|
||||
'<div prototype-method-name-as-scope-var-c></div>'
|
||||
)($rootScope);
|
||||
};
|
||||
expect(func).toThrowMinErr('$compile',
|
||||
'missingattr',
|
||||
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
|
||||
'ScopeVarC\' is non-optional and must be set!');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error for set non-optional "&" bindings when ' +
|
||||
'strictComponentBindingsEnabled is true', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
});
|
||||
inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
element = $compile(
|
||||
'<div prototype-method-name-as-scope-var-c constructor="constructor" value-of="valueOf"></div>'
|
||||
)($rootScope);
|
||||
};
|
||||
expect(func).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error for undefined optional "&" bindings when ' +
|
||||
'strictComponentBindingsEnabled is true', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
});
|
||||
inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
element = $compile(
|
||||
'<div prototype-method-name-as-scope-var-c value-of="valueOf"></div>'
|
||||
)($rootScope);
|
||||
};
|
||||
expect(func).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error for undefined non-optional "<" bindings when ' +
|
||||
'strictComponentBindingsEnabled is true', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
});
|
||||
inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
element = $compile(
|
||||
'<div prototype-method-name-as-scope-var-d></div>'
|
||||
)($rootScope);
|
||||
};
|
||||
expect(func).toThrowMinErr('$compile',
|
||||
'missingattr',
|
||||
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
|
||||
'ScopeVarD\' is non-optional and must be set!');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error for set non-optional "<" bindings when ' +
|
||||
'strictComponentBindingsEnabled is true', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
});
|
||||
inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
element = $compile(
|
||||
'<div prototype-method-name-as-scope-var-d constructor="constructor" value-of="valueOf"></div>'
|
||||
)($rootScope);
|
||||
};
|
||||
expect(func).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw an error for undefined optional "<" bindings when ' +
|
||||
'strictComponentBindingsEnabled is true', function() {
|
||||
module(function($compileProvider) {
|
||||
$compileProvider.strictComponentBindingsEnabled(true);
|
||||
});
|
||||
inject(
|
||||
function($rootScope, $compile) {
|
||||
var func = function() {
|
||||
element = $compile(
|
||||
'<div prototype-method-name-as-scope-var-d value-of="valueOf"></div>'
|
||||
)($rootScope);
|
||||
};
|
||||
expect(func).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw exception when using "watch" as binding in Firefox', inject(
|
||||
function($rootScope, $compile) {
|
||||
$rootScope.watch = 'watch';
|
||||
|
||||
Reference in New Issue
Block a user