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

Commit 6a33834

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

File tree

3 files changed

+272
-0
lines changed

3 files changed

+272
-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: 41 additions & 0 deletions
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

Lines changed: 223 additions & 0 deletions
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,16 @@ 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+
});
25492568
directive('watchAsScopeVar', function() {
25502569
return {
25512570
scope: {
@@ -2854,6 +2873,57 @@ describe('$compile', function() {
28542873
})
28552874
);
28562875

2876+
it('should throw an error for undefined non-optional "=" bindings when ' +
2877+
'strictComponentBindingsEnabled is true', function() {
2878+
module(function($compileProvider) {
2879+
$compileProvider.strictComponentBindingsEnabled(true);
2880+
});
2881+
inject(
2882+
function($rootScope, $compile) {
2883+
var func = function() {
2884+
element = $compile(
2885+
'<div prototype-method-name-as-scope-var-a></div>'
2886+
)($rootScope);
2887+
};
2888+
expect(func).toThrowMinErr('$compile',
2889+
'missingattr',
2890+
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
2891+
'ScopeVarA\' is non-optional and must be set!');
2892+
});
2893+
});
2894+
2895+
it('should not throw an error for set non-optional "=" bindings when ' +
2896+
'strictComponentBindingsEnabled is true', function() {
2897+
module(function($compileProvider) {
2898+
$compileProvider.strictComponentBindingsEnabled(true);
2899+
});
2900+
inject(
2901+
function($rootScope, $compile) {
2902+
var func = function() {
2903+
element = $compile(
2904+
'<div prototype-method-name-as-scope-var-a constructor="constructor" value-of="valueOf"></div>'
2905+
)($rootScope);
2906+
};
2907+
expect(func).not.toThrow();
2908+
});
2909+
});
2910+
2911+
it('should not throw an error for undefined optional "=" bindings when ' +
2912+
'strictComponentBindingsEnabled is true', function() {
2913+
module(function($compileProvider) {
2914+
$compileProvider.strictComponentBindingsEnabled(true);
2915+
});
2916+
inject(
2917+
function($rootScope, $compile) {
2918+
var func = function() {
2919+
element = $compile(
2920+
'<div prototype-method-name-as-scope-var-a value-of="valueOf"></div>'
2921+
)($rootScope);
2922+
};
2923+
expect(func).not.toThrow();
2924+
});
2925+
});
2926+
28572927
it('should handle "@" bindings with same method names in Object.prototype correctly when not present', inject(
28582928
function($rootScope, $compile) {
28592929
var func = function() {
@@ -2891,6 +2961,57 @@ describe('$compile', function() {
28912961
})
28922962
);
28932963

2964+
it('should throw an error for undefined non-optional "@" bindings when ' +
2965+
'strictComponentBindingsEnabled is true', function() {
2966+
module(function($compileProvider) {
2967+
$compileProvider.strictComponentBindingsEnabled(true);
2968+
});
2969+
inject(
2970+
function($rootScope, $compile) {
2971+
var func = function() {
2972+
element = $compile(
2973+
'<div prototype-method-name-as-scope-var-b></div>'
2974+
)($rootScope);
2975+
};
2976+
expect(func).toThrowMinErr('$compile',
2977+
'missingattr',
2978+
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
2979+
'ScopeVarB\' is non-optional and must be set!');
2980+
});
2981+
});
2982+
2983+
it('should not throw an error for set non-optional "@" bindings when ' +
2984+
'strictComponentBindingsEnabled is true', function() {
2985+
module(function($compileProvider) {
2986+
$compileProvider.strictComponentBindingsEnabled(true);
2987+
});
2988+
inject(
2989+
function($rootScope, $compile) {
2990+
var func = function() {
2991+
element = $compile(
2992+
'<div prototype-method-name-as-scope-var-b constructor="constructor" value-of="valueOf"></div>'
2993+
)($rootScope);
2994+
};
2995+
expect(func).not.toThrow();
2996+
});
2997+
});
2998+
2999+
it('should not throw an error for undefined optional "@" bindings when ' +
3000+
'strictComponentBindingsEnabled is true', function() {
3001+
module(function($compileProvider) {
3002+
$compileProvider.strictComponentBindingsEnabled(true);
3003+
});
3004+
inject(
3005+
function($rootScope, $compile) {
3006+
var func = function() {
3007+
element = $compile(
3008+
'<div prototype-method-name-as-scope-var-b value-of="valueOf"></div>'
3009+
)($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,108 @@ 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 not throw an error for set non-optional "&" bindings when ' +
3067+
'strictComponentBindingsEnabled is true', function() {
3068+
module(function($compileProvider) {
3069+
$compileProvider.strictComponentBindingsEnabled(true);
3070+
});
3071+
inject(
3072+
function($rootScope, $compile) {
3073+
var func = function() {
3074+
element = $compile(
3075+
'<div prototype-method-name-as-scope-var-c constructor="constructor" value-of="valueOf"></div>'
3076+
)($rootScope);
3077+
};
3078+
expect(func).not.toThrow();
3079+
});
3080+
});
3081+
3082+
it('should not throw an error for undefined optional "&" bindings when ' +
3083+
'strictComponentBindingsEnabled is true', function() {
3084+
module(function($compileProvider) {
3085+
$compileProvider.strictComponentBindingsEnabled(true);
3086+
});
3087+
inject(
3088+
function($rootScope, $compile) {
3089+
var func = function() {
3090+
element = $compile(
3091+
'<div prototype-method-name-as-scope-var-c value-of="valueOf"></div>'
3092+
)($rootScope);
3093+
};
3094+
expect(func).not.toThrow();
3095+
});
3096+
});
3097+
3098+
it('should throw an error for undefined non-optional "<" bindings when ' +
3099+
'strictComponentBindingsEnabled is true', function() {
3100+
module(function($compileProvider) {
3101+
$compileProvider.strictComponentBindingsEnabled(true);
3102+
});
3103+
inject(
3104+
function($rootScope, $compile) {
3105+
var func = function() {
3106+
element = $compile(
3107+
'<div prototype-method-name-as-scope-var-d></div>'
3108+
)($rootScope);
3109+
};
3110+
expect(func).toThrowMinErr('$compile',
3111+
'missingattr',
3112+
'Attribute \'valueOf\' of \'prototypeMethodNameAs' +
3113+
'ScopeVarD\' is non-optional and must be set!');
3114+
});
3115+
});
3116+
3117+
it('should not throw an error for set non-optional "<" bindings when ' +
3118+
'strictComponentBindingsEnabled is true', function() {
3119+
module(function($compileProvider) {
3120+
$compileProvider.strictComponentBindingsEnabled(true);
3121+
});
3122+
inject(
3123+
function($rootScope, $compile) {
3124+
var func = function() {
3125+
element = $compile(
3126+
'<div prototype-method-name-as-scope-var-d constructor="constructor" value-of="valueOf"></div>'
3127+
)($rootScope);
3128+
};
3129+
expect(func).not.toThrow();
3130+
});
3131+
});
3132+
3133+
it('should not throw an error for undefined optional "<" bindings when ' +
3134+
'strictComponentBindingsEnabled is true', function() {
3135+
module(function($compileProvider) {
3136+
$compileProvider.strictComponentBindingsEnabled(true);
3137+
});
3138+
inject(
3139+
function($rootScope, $compile) {
3140+
var func = function() {
3141+
element = $compile(
3142+
'<div prototype-method-name-as-scope-var-d value-of="valueOf"></div>'
3143+
)($rootScope);
3144+
};
3145+
expect(func).not.toThrow();
3146+
});
3147+
});
3148+
29263149
it('should not throw exception when using "watch" as binding in Firefox', inject(
29273150
function($rootScope, $compile) {
29283151
$rootScope.watch = 'watch';

0 commit comments

Comments
 (0)