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

Commit 6053c8d

Browse files
committed
feat($compile): added new method strictComponentBindingsEnabled
1 parent e6d5fe7 commit 6053c8d

File tree

3 files changed

+236
-0
lines changed

3 files changed

+236
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@ngdoc error
2+
@name $compile:missingattr
3+
@fullName Missing required attribute
4+
@description
5+
6+
This error may occur only when `$compileProvider.strictComponentBindingsEnabled` is set to `true`.
7+
Then all attributes mentioned in `bindings` without `?` must be set. If one or more aren't set,
8+
the first one will throw an error.

src/ng/compile.js

+41
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,32 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
14031403
return debugInfoEnabled;
14041404
};
14051405

1406+
/**
1407+
* @ngdoc method
1408+
* @name $compileProvider#strictComponentBindingsEnabled
1409+
*
1410+
* @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided, otherwise just return the
1411+
* current strictComponentBindingsEnabled state
1412+
* @returns {*} current value if used as getter or itself (chaining) if used as setter
1413+
*
1414+
* @kind function
1415+
*
1416+
* @description
1417+
* Call this method to enable/disable strict component bindings check. If enabled, the compiler will enforce that
1418+
* for all bindings of a component that are not set as optional with `?`, an attribute needs to be provided
1419+
* on the component's HTML tag.
1420+
*
1421+
* The default value is false.
1422+
*/
1423+
var strictComponentBindingsEnabled = false;
1424+
this.strictComponentBindingsEnabled = function(enabled) {
1425+
if (isDefined(enabled)) {
1426+
strictComponentBindingsEnabled = enabled;
1427+
return this;
1428+
}
1429+
return strictComponentBindingsEnabled;
1430+
};
1431+
14061432
var TTL = 10;
14071433
/**
14081434
* @ngdoc method
@@ -3413,12 +3439,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
34133439
}
34143440
}
34153441

3442+
function strictBindingsCheck(attrName, directiveName) {
3443+
if (strictComponentBindingsEnabled) {
3444+
throw $compileMinErr('missingattr',
3445+
'Attribute \'{0}\' of \'{1}\' is non-optional and must be set!',
3446+
attrName, directiveName);
3447+
}
3448+
}
34163449

34173450
// Set up $watches for isolate scope and controller bindings.
34183451
function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
34193452
var removeWatchCollection = [];
34203453
var initialChanges = {};
34213454
var changes;
3455+
34223456
forEach(bindings, function initializeBinding(definition, scopeName) {
34233457
var attrName = definition.attrName,
34243458
optional = definition.optional,
@@ -3430,7 +3464,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
34303464

34313465
case '@':
34323466
if (!optional && !hasOwnProperty.call(attrs, attrName)) {
3467+
strictBindingsCheck(attrName, directive.name);
34333468
destination[scopeName] = attrs[attrName] = undefined;
3469+
34343470
}
34353471
removeWatch = attrs.$observe(attrName, function(value) {
34363472
if (isString(value) || isBoolean(value)) {
@@ -3457,6 +3493,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
34573493
case '=':
34583494
if (!hasOwnProperty.call(attrs, attrName)) {
34593495
if (optional) break;
3496+
strictBindingsCheck(attrName, directive.name);
34603497
attrs[attrName] = undefined;
34613498
}
34623499
if (optional && !attrs[attrName]) break;
@@ -3501,6 +3538,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
35013538
case '<':
35023539
if (!hasOwnProperty.call(attrs, attrName)) {
35033540
if (optional) break;
3541+
strictBindingsCheck(attrName, directive.name);
35043542
attrs[attrName] = undefined;
35053543
}
35063544
if (optional && !attrs[attrName]) break;
@@ -3526,6 +3564,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
35263564
break;
35273565

35283566
case '&':
3567+
if (!optional && !hasOwnProperty.call(attrs, attrName)) {
3568+
strictBindingsCheck(attrName, directive.name);
3569+
}
35293570
// Don't assign Object.prototype method to scope
35303571
parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
35313572

test/ng/compileSpec.js

+187
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,15 @@ describe('$compile', function() {
169169
inject();
170170
});
171171

172+
it('should allow strictComponentBindingsEnabled to be configured', function() {
173+
module(function($compileProvider) {
174+
expect($compileProvider.strictComponentBindingsEnabled()).toBe(false); // the default
175+
$compileProvider.strictComponentBindingsEnabled(true);
176+
expect($compileProvider.strictComponentBindingsEnabled()).toBe(true);
177+
});
178+
inject();
179+
});
180+
172181
it('should allow onChangesTtl to be configured', function() {
173182
module(function($compileProvider) {
174183
expect($compileProvider.onChangesTtl()).toBe(10); // the default
@@ -2546,6 +2555,52 @@ describe('$compile', function() {
25462555
template: '<span></span>'
25472556
};
25482557
});
2558+
directive('prototypeMethodNameAsScopeVarD', function() {
2559+
return {
2560+
scope: {
2561+
'constructor': '<?',
2562+
'valueOf': '<'
2563+
},
2564+
restrict: 'AE',
2565+
template: '<span></span>'
2566+
};
2567+
});
2568+
directive('optionalBindingsVarA', function() {
2569+
return {
2570+
scope: {
2571+
'variable': '=?'
2572+
},
2573+
restrict: 'AE',
2574+
template: '<span></span>'
2575+
};
2576+
});
2577+
directive('optionalBindingsVarB', function() {
2578+
return {
2579+
scope: {
2580+
'variable': '@?'
2581+
},
2582+
restrict: 'AE',
2583+
template: '<span></span>'
2584+
};
2585+
});
2586+
directive('optionalBindingsVarC', function() {
2587+
return {
2588+
scope: {
2589+
'variable': '&?'
2590+
},
2591+
restrict: 'AE',
2592+
template: '<span></span>'
2593+
};
2594+
});
2595+
directive('optionalBindingsVarD', function() {
2596+
return {
2597+
scope: {
2598+
'variable': '<?'
2599+
},
2600+
restrict: 'AE',
2601+
template: '<span></span>'
2602+
};
2603+
});
25492604
directive('watchAsScopeVar', function() {
25502605
return {
25512606
scope: {
@@ -2854,6 +2909,39 @@ describe('$compile', function() {
28542909
})
28552910
);
28562911

2912+
it('should throw an error for undefined non-optional "=" bindings when ' +
2913+
'strictComponentBindingsEnabled is true', function() {
2914+
module(function($compileProvider) {
2915+
$compileProvider.strictComponentBindingsEnabled(true);
2916+
});
2917+
inject(
2918+
function($rootScope, $compile) {
2919+
var func = function() {
2920+
element = $compile(
2921+
'<div prototype-method-name-as-scope-var-a></div>'
2922+
)($rootScope);
2923+
};
2924+
expect(func).toThrowMinErr('$compile',
2925+
'missingattr',
2926+
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
2927+
'ScopeVarA\' is non-optional and must be set!');
2928+
});
2929+
});
2930+
2931+
it('should handle "=" undefined optional bindings when strictComponentBindingsEnabled is true',
2932+
function() {
2933+
module(function ($compileProvider) {
2934+
$compileProvider.strictComponentBindingsEnabled(true);
2935+
});
2936+
inject(
2937+
function ($rootScope, $compile) {
2938+
var func = function () {
2939+
element = $compile('<div optional-bindings-var-a></div>')($rootScope);
2940+
};
2941+
expect(func).not.toThrow();
2942+
});
2943+
});
2944+
28572945
it('should handle "@" bindings with same method names in Object.prototype correctly when not present', inject(
28582946
function($rootScope, $compile) {
28592947
var func = function() {
@@ -2891,6 +2979,39 @@ describe('$compile', function() {
28912979
})
28922980
);
28932981

2982+
it('should throw an error for undefined non-optional "@" bindings when ' +
2983+
'strictComponentBindingsEnabled is true', function() {
2984+
module(function($compileProvider) {
2985+
$compileProvider.strictComponentBindingsEnabled(true);
2986+
});
2987+
inject(
2988+
function($rootScope, $compile) {
2989+
var func = function() {
2990+
element = $compile(
2991+
'<div prototype-method-name-as-scope-var-b></div>'
2992+
)($rootScope);
2993+
};
2994+
expect(func).toThrowMinErr('$compile',
2995+
'missingattr',
2996+
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
2997+
'ScopeVarB\' is non-optional and must be set!');
2998+
});
2999+
});
3000+
3001+
it('should handle "@" undefined optional bindings when strictComponentBindingsEnabled is true',
3002+
function() {
3003+
module(function ($compileProvider) {
3004+
$compileProvider.strictComponentBindingsEnabled(true);
3005+
});
3006+
inject(
3007+
function ($rootScope, $compile) {
3008+
var func = function () {
3009+
element = $compile('<div optional-bindings-var-b></div>')($rootScope);
3010+
};
3011+
expect(func).not.toThrow();
3012+
});
3013+
});
3014+
28943015
it('should handle "&" bindings with same method names in Object.prototype correctly when not present', inject(
28953016
function($rootScope, $compile) {
28963017
var func = function() {
@@ -2923,6 +3044,72 @@ describe('$compile', function() {
29233044
})
29243045
);
29253046

3047+
it('should throw an error for undefined non-optional "&" bindings when ' +
3048+
'strictComponentBindingsEnabled is true', function() {
3049+
module(function($compileProvider) {
3050+
$compileProvider.strictComponentBindingsEnabled(true);
3051+
});
3052+
inject(
3053+
function($rootScope, $compile) {
3054+
var func = function() {
3055+
element = $compile(
3056+
'<div prototype-method-name-as-scope-var-c></div>'
3057+
)($rootScope);
3058+
};
3059+
expect(func).toThrowMinErr('$compile',
3060+
'missingattr',
3061+
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
3062+
'ScopeVarC\' is non-optional and must be set!');
3063+
});
3064+
});
3065+
3066+
it('should handle "&" undefined optional bindings when strictComponentBindingsEnabled is true',
3067+
function() {
3068+
module(function ($compileProvider) {
3069+
$compileProvider.strictComponentBindingsEnabled(true);
3070+
});
3071+
inject(
3072+
function ($rootScope, $compile) {
3073+
var func = function () {
3074+
element = $compile('<div optional-bindings-var-c></div>')($rootScope);
3075+
};
3076+
expect(func).not.toThrow();
3077+
});
3078+
});
3079+
3080+
it('should throw an error for undefined non-optional ">" bindings when ' +
3081+
'strictComponentBindingsEnabled is true', function() {
3082+
module(function($compileProvider) {
3083+
$compileProvider.strictComponentBindingsEnabled(true);
3084+
});
3085+
inject(
3086+
function($rootScope, $compile) {
3087+
var func = function() {
3088+
element = $compile(
3089+
'<div prototype-method-name-as-scope-var-d></div>'
3090+
)($rootScope);
3091+
};
3092+
expect(func).toThrowMinErr('$compile',
3093+
'missingattr',
3094+
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
3095+
'ScopeVarD\' is non-optional and must be set!');
3096+
});
3097+
});
3098+
3099+
it('should handle ">" undefined optional bindings when strictComponentBindingsEnabled is true',
3100+
function() {
3101+
module(function ($compileProvider) {
3102+
$compileProvider.strictComponentBindingsEnabled(true);
3103+
});
3104+
inject(
3105+
function ($rootScope, $compile) {
3106+
var func = function () {
3107+
element = $compile('<div optional-bindings-var-d></div>')($rootScope);
3108+
};
3109+
expect(func).not.toThrow();
3110+
});
3111+
});
3112+
29263113
it('should not throw exception when using "watch" as binding in Firefox', inject(
29273114
function($rootScope, $compile) {
29283115
$rootScope.watch = 'watch';

0 commit comments

Comments
 (0)