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

Commit 3ec1819

Browse files
ZitaNemeckovagkalpak
authored andcommitted
feat($compile): add strictComponentBindingsEnabled() method
Closes #16129
1 parent 009ebec commit 3ec1819

File tree

3 files changed

+271
-0
lines changed

3 files changed

+271
-0
lines changed
Lines changed: 8 additions & 0 deletions
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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,31 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
14141414
return preAssignBindingsEnabled;
14151415
};
14161416

1417+
/**
1418+
* @ngdoc method
1419+
* @name $compileProvider#strictComponentBindingsEnabled
1420+
*
1421+
* @param {boolean=} enabled update the strictComponentBindingsEnabled state if provided, otherwise just return the
1422+
* current strictComponentBindingsEnabled state
1423+
* @returns {*} current value if used as getter or itself (chaining) if used as setter
1424+
*
1425+
* @kind function
1426+
*
1427+
* @description
1428+
* Call this method to enable/disable strict component bindings check. If enabled, the compiler will enforce that
1429+
* for all bindings of a component that are not set as optional with `?`, an attribute needs to be provided
1430+
* on the component's HTML tag.
1431+
*
1432+
* The default value is false.
1433+
*/
1434+
var strictComponentBindingsEnabled = false;
1435+
this.strictComponentBindingsEnabled = function(enabled) {
1436+
if (isDefined(enabled)) {
1437+
strictComponentBindingsEnabled = enabled;
1438+
return this;
1439+
}
1440+
return strictComponentBindingsEnabled;
1441+
};
14171442

14181443
var TTL = 10;
14191444
/**
@@ -3441,12 +3466,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
34413466
}
34423467
}
34433468

3469+
function strictBindingsCheck(attrName, directiveName) {
3470+
if (strictComponentBindingsEnabled) {
3471+
throw $compileMinErr('missingattr',
3472+
'Attribute \'{0}\' of \'{1}\' is non-optional and must be set!',
3473+
attrName, directiveName);
3474+
}
3475+
}
34443476

34453477
// Set up $watches for isolate scope and controller bindings.
34463478
function initializeDirectiveBindings(scope, attrs, destination, bindings, directive) {
34473479
var removeWatchCollection = [];
34483480
var initialChanges = {};
34493481
var changes;
3482+
34503483
forEach(bindings, function initializeBinding(definition, scopeName) {
34513484
var attrName = definition.attrName,
34523485
optional = definition.optional,
@@ -3458,7 +3491,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
34583491

34593492
case '@':
34603493
if (!optional && !hasOwnProperty.call(attrs, attrName)) {
3494+
strictBindingsCheck(attrName, directive.name);
34613495
destination[scopeName] = attrs[attrName] = undefined;
3496+
34623497
}
34633498
removeWatch = attrs.$observe(attrName, function(value) {
34643499
if (isString(value) || isBoolean(value)) {
@@ -3485,6 +3520,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
34853520
case '=':
34863521
if (!hasOwnProperty.call(attrs, attrName)) {
34873522
if (optional) break;
3523+
strictBindingsCheck(attrName, directive.name);
34883524
attrs[attrName] = undefined;
34893525
}
34903526
if (optional && !attrs[attrName]) break;
@@ -3529,6 +3565,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
35293565
case '<':
35303566
if (!hasOwnProperty.call(attrs, attrName)) {
35313567
if (optional) break;
3568+
strictBindingsCheck(attrName, directive.name);
35323569
attrs[attrName] = undefined;
35333570
}
35343571
if (optional && !attrs[attrName]) break;
@@ -3554,6 +3591,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
35543591
break;
35553592

35563593
case '&':
3594+
if (!optional && !hasOwnProperty.call(attrs, attrName)) {
3595+
strictBindingsCheck(attrName, directive.name);
3596+
}
35573597
// Don't assign Object.prototype method to scope
35583598
parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;
35593599

test/ng/compileSpec.js

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,15 @@ describe('$compile', function() {
180180
inject();
181181
});
182182

183+
it('should allow strictComponentBindingsEnabled to be configured', function() {
184+
module(function($compileProvider) {
185+
expect($compileProvider.strictComponentBindingsEnabled()).toBe(false); // the default
186+
$compileProvider.strictComponentBindingsEnabled(true);
187+
expect($compileProvider.strictComponentBindingsEnabled()).toBe(true);
188+
});
189+
inject();
190+
});
191+
183192
it('should allow onChangesTtl to be configured', function() {
184193
module(function($compileProvider) {
185194
expect($compileProvider.onChangesTtl()).toBe(10); // the default
@@ -2557,6 +2566,16 @@ describe('$compile', function() {
25572566
template: '<span></span>'
25582567
};
25592568
});
2569+
directive('prototypeMethodNameAsScopeVarD', function() {
2570+
return {
2571+
scope: {
2572+
'constructor': '<?',
2573+
'valueOf': '<'
2574+
},
2575+
restrict: 'AE',
2576+
template: '<span></span>'
2577+
};
2578+
});
25602579
directive('watchAsScopeVar', function() {
25612580
return {
25622581
scope: {
@@ -2865,6 +2884,57 @@ describe('$compile', function() {
28652884
})
28662885
);
28672886

2887+
it('should throw an error for undefined non-optional "=" bindings when ' +
2888+
'strictComponentBindingsEnabled is true', function() {
2889+
module(function($compileProvider) {
2890+
$compileProvider.strictComponentBindingsEnabled(true);
2891+
});
2892+
inject(
2893+
function($rootScope, $compile) {
2894+
var func = function() {
2895+
element = $compile(
2896+
'<div prototype-method-name-as-scope-var-a></div>'
2897+
)($rootScope);
2898+
};
2899+
expect(func).toThrowMinErr('$compile',
2900+
'missingattr',
2901+
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
2902+
'ScopeVarA\' is non-optional and must be set!');
2903+
});
2904+
});
2905+
2906+
it('should not throw an error for set non-optional "=" bindings when ' +
2907+
'strictComponentBindingsEnabled is true', function() {
2908+
module(function($compileProvider) {
2909+
$compileProvider.strictComponentBindingsEnabled(true);
2910+
});
2911+
inject(
2912+
function($rootScope, $compile) {
2913+
var func = function() {
2914+
element = $compile(
2915+
'<div prototype-method-name-as-scope-var-a constructor="constructor" value-of="valueOf"></div>'
2916+
)($rootScope);
2917+
};
2918+
expect(func).not.toThrow();
2919+
});
2920+
});
2921+
2922+
it('should not throw an error for undefined optional "=" bindings when ' +
2923+
'strictComponentBindingsEnabled is true', function() {
2924+
module(function($compileProvider) {
2925+
$compileProvider.strictComponentBindingsEnabled(true);
2926+
});
2927+
inject(
2928+
function($rootScope, $compile) {
2929+
var func = function() {
2930+
element = $compile(
2931+
'<div prototype-method-name-as-scope-var-a value-of="valueOf"></div>'
2932+
)($rootScope);
2933+
};
2934+
expect(func).not.toThrow();
2935+
});
2936+
});
2937+
28682938
it('should handle "@" bindings with same method names in Object.prototype correctly when not present', inject(
28692939
function($rootScope, $compile) {
28702940
var func = function() {
@@ -2902,6 +2972,57 @@ describe('$compile', function() {
29022972
})
29032973
);
29042974

2975+
it('should throw an error for undefined non-optional "@" bindings when ' +
2976+
'strictComponentBindingsEnabled is true', function() {
2977+
module(function($compileProvider) {
2978+
$compileProvider.strictComponentBindingsEnabled(true);
2979+
});
2980+
inject(
2981+
function($rootScope, $compile) {
2982+
var func = function() {
2983+
element = $compile(
2984+
'<div prototype-method-name-as-scope-var-b></div>'
2985+
)($rootScope);
2986+
};
2987+
expect(func).toThrowMinErr('$compile',
2988+
'missingattr',
2989+
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
2990+
'ScopeVarB\' is non-optional and must be set!');
2991+
});
2992+
});
2993+
2994+
it('should not throw an error for set non-optional "@" bindings when ' +
2995+
'strictComponentBindingsEnabled is true', function() {
2996+
module(function($compileProvider) {
2997+
$compileProvider.strictComponentBindingsEnabled(true);
2998+
});
2999+
inject(
3000+
function($rootScope, $compile) {
3001+
var func = function() {
3002+
element = $compile(
3003+
'<div prototype-method-name-as-scope-var-b constructor="constructor" value-of="valueOf"></div>'
3004+
)($rootScope);
3005+
};
3006+
expect(func).not.toThrow();
3007+
});
3008+
});
3009+
3010+
it('should not throw an error for undefined optional "@" bindings when ' +
3011+
'strictComponentBindingsEnabled is true', function() {
3012+
module(function($compileProvider) {
3013+
$compileProvider.strictComponentBindingsEnabled(true);
3014+
});
3015+
inject(
3016+
function($rootScope, $compile) {
3017+
var func = function() {
3018+
element = $compile(
3019+
'<div prototype-method-name-as-scope-var-b value-of="valueOf"></div>'
3020+
)($rootScope);
3021+
};
3022+
expect(func).not.toThrow();
3023+
});
3024+
});
3025+
29053026
it('should handle "&" bindings with same method names in Object.prototype correctly when not present', inject(
29063027
function($rootScope, $compile) {
29073028
var func = function() {
@@ -2934,6 +3055,108 @@ describe('$compile', function() {
29343055
})
29353056
);
29363057

3058+
it('should throw an error for undefined non-optional "&" bindings when ' +
3059+
'strictComponentBindingsEnabled is true', function() {
3060+
module(function($compileProvider) {
3061+
$compileProvider.strictComponentBindingsEnabled(true);
3062+
});
3063+
inject(
3064+
function($rootScope, $compile) {
3065+
var func = function() {
3066+
element = $compile(
3067+
'<div prototype-method-name-as-scope-var-c></div>'
3068+
)($rootScope);
3069+
};
3070+
expect(func).toThrowMinErr('$compile',
3071+
'missingattr',
3072+
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
3073+
'ScopeVarC\' is non-optional and must be set!');
3074+
});
3075+
});
3076+
3077+
it('should not throw an error for set non-optional "&" bindings when ' +
3078+
'strictComponentBindingsEnabled is true', function() {
3079+
module(function($compileProvider) {
3080+
$compileProvider.strictComponentBindingsEnabled(true);
3081+
});
3082+
inject(
3083+
function($rootScope, $compile) {
3084+
var func = function() {
3085+
element = $compile(
3086+
'<div prototype-method-name-as-scope-var-c constructor="constructor" value-of="valueOf"></div>'
3087+
)($rootScope);
3088+
};
3089+
expect(func).not.toThrow();
3090+
});
3091+
});
3092+
3093+
it('should not throw an error for undefined optional "&" bindings when ' +
3094+
'strictComponentBindingsEnabled is true', function() {
3095+
module(function($compileProvider) {
3096+
$compileProvider.strictComponentBindingsEnabled(true);
3097+
});
3098+
inject(
3099+
function($rootScope, $compile) {
3100+
var func = function() {
3101+
element = $compile(
3102+
'<div prototype-method-name-as-scope-var-c value-of="valueOf"></div>'
3103+
)($rootScope);
3104+
};
3105+
expect(func).not.toThrow();
3106+
});
3107+
});
3108+
3109+
it('should throw an error for undefined non-optional "<" bindings when ' +
3110+
'strictComponentBindingsEnabled is true', function() {
3111+
module(function($compileProvider) {
3112+
$compileProvider.strictComponentBindingsEnabled(true);
3113+
});
3114+
inject(
3115+
function($rootScope, $compile) {
3116+
var func = function() {
3117+
element = $compile(
3118+
'<div prototype-method-name-as-scope-var-d></div>'
3119+
)($rootScope);
3120+
};
3121+
expect(func).toThrowMinErr('$compile',
3122+
'missingattr',
3123+
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
3124+
'ScopeVarD\' is non-optional and must be set!');
3125+
});
3126+
});
3127+
3128+
it('should not throw an error for set non-optional "<" bindings when ' +
3129+
'strictComponentBindingsEnabled is true', function() {
3130+
module(function($compileProvider) {
3131+
$compileProvider.strictComponentBindingsEnabled(true);
3132+
});
3133+
inject(
3134+
function($rootScope, $compile) {
3135+
var func = function() {
3136+
element = $compile(
3137+
'<div prototype-method-name-as-scope-var-d constructor="constructor" value-of="valueOf"></div>'
3138+
)($rootScope);
3139+
};
3140+
expect(func).not.toThrow();
3141+
});
3142+
});
3143+
3144+
it('should not throw an error for undefined optional "<" bindings when ' +
3145+
'strictComponentBindingsEnabled is true', function() {
3146+
module(function($compileProvider) {
3147+
$compileProvider.strictComponentBindingsEnabled(true);
3148+
});
3149+
inject(
3150+
function($rootScope, $compile) {
3151+
var func = function() {
3152+
element = $compile(
3153+
'<div prototype-method-name-as-scope-var-d value-of="valueOf"></div>'
3154+
)($rootScope);
3155+
};
3156+
expect(func).not.toThrow();
3157+
});
3158+
});
3159+
29373160
it('should not throw exception when using "watch" as binding in Firefox', inject(
29383161
function($rootScope, $compile) {
29393162
$rootScope.watch = 'watch';

0 commit comments

Comments
 (0)