feat($compile): change directive's restrict setting to default to EA (element/attribute)

Previously we defaulted just to A because of IE8 which had a hard time with applying css styles to HTMLUnknownElements.

This is no longer the case with IE9, so we should make restrict default to EA. Doing so will make it easier to create
components and avoid matching errors when creating new directives

BREAKING CHANGE: directives now match elements by default unless specific restriction rules are set via `restrict` property.

This means that if a directive 'myFoo' previously didn't specify matching restrictrion, it will now match both the attribute
and element form.

Before:

 <div my-foo></div> <---- my-foo attribute matched the directive
 <my-foo></my-foo>  <---- no match

After:

 <div my-foo></div> <---- my-foo attribute matched the directive
 <my-foo></my-foo>  <---- my-foo element matched the directive

It is not expected that this will be a problem in practice because of widespread use of prefixes that make "<my-foo>" like
elements unlikely.

Closes #8321
This commit is contained in:
Igor Minar
2014-07-24 08:20:21 -07:00
parent 925b2080a0
commit 11f5aeeee9
10 changed files with 63 additions and 20 deletions

View File

@@ -213,7 +213,7 @@
* String of subset of `EACM` which restricts the directive to a specific directive * String of subset of `EACM` which restricts the directive to a specific directive
* declaration style. If omitted, the default (attributes only) is used. * declaration style. If omitted, the default (attributes only) is used.
* *
* * `E` - Element name: `<my-directive></my-directive>` * * `E` - Element name (default): `<my-directive></my-directive>`
* * `A` - Attribute (default): `<div my-directive="exp"></div>` * * `A` - Attribute (default): `<div my-directive="exp"></div>`
* * `C` - Class: `<div class="my-directive: exp;"></div>` * * `C` - Class: `<div class="my-directive: exp;"></div>`
* * `M` - Comment: `<!-- directive: my-directive exp -->` * * `M` - Comment: `<!-- directive: my-directive exp -->`
@@ -581,7 +581,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
directive.index = index; directive.index = index;
directive.name = directive.name || name; directive.name = directive.name || name;
directive.require = directive.require || (directive.controller && directive.name); directive.require = directive.require || (directive.controller && directive.name);
directive.restrict = directive.restrict || 'A'; directive.restrict = directive.restrict || 'EA';
directives.push(directive); directives.push(directive);
} catch (e) { } catch (e) {
$exceptionHandler(e); $exceptionHandler(e);

View File

@@ -351,6 +351,7 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) {
var normalized = directiveNormalize('ng-' + attrName); var normalized = directiveNormalize('ng-' + attrName);
ngAttributeAliasDirectives[normalized] = function() { ngAttributeAliasDirectives[normalized] = function() {
return { return {
restrict: 'A',
priority: 100, priority: 100,
link: function(scope, element, attr) { link: function(scope, element, attr) {
scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {

View File

@@ -2150,6 +2150,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
*/ */
var ngModelDirective = function() { var ngModelDirective = function() {
return { return {
restrict: 'A',
require: ['ngModel', '^?form', '^?ngModelOptions'], require: ['ngModel', '^?form', '^?ngModelOptions'],
controller: NgModelController, controller: NgModelController,
link: { link: {
@@ -2251,6 +2252,7 @@ var ngModelDirective = function() {
* </example> * </example>
*/ */
var ngChangeDirective = valueFn({ var ngChangeDirective = valueFn({
restrict: 'A',
require: 'ngModel', require: 'ngModel',
link: function(scope, element, attr, ctrl) { link: function(scope, element, attr, ctrl) {
ctrl.$viewChangeListeners.push(function() { ctrl.$viewChangeListeners.push(function() {
@@ -2262,6 +2264,7 @@ var ngChangeDirective = valueFn({
var requiredDirective = function() { var requiredDirective = function() {
return { return {
restrict: 'A',
require: '?ngModel', require: '?ngModel',
link: function(scope, elm, attr, ctrl) { link: function(scope, elm, attr, ctrl) {
if (!ctrl) return; if (!ctrl) return;
@@ -2281,6 +2284,7 @@ var requiredDirective = function() {
var patternDirective = function() { var patternDirective = function() {
return { return {
restrict: 'A',
require: '?ngModel', require: '?ngModel',
link: function(scope, elm, attr, ctrl) { link: function(scope, elm, attr, ctrl) {
if (!ctrl) return; if (!ctrl) return;
@@ -2311,6 +2315,7 @@ var patternDirective = function() {
var maxlengthDirective = function() { var maxlengthDirective = function() {
return { return {
restrict: 'A',
require: '?ngModel', require: '?ngModel',
link: function(scope, elm, attr, ctrl) { link: function(scope, elm, attr, ctrl) {
if (!ctrl) return; if (!ctrl) return;
@@ -2329,6 +2334,7 @@ var maxlengthDirective = function() {
var minlengthDirective = function() { var minlengthDirective = function() {
return { return {
restrict: 'A',
require: '?ngModel', require: '?ngModel',
link: function(scope, elm, attr, ctrl) { link: function(scope, elm, attr, ctrl) {
if (!ctrl) return; if (!ctrl) return;
@@ -2430,6 +2436,7 @@ var minlengthDirective = function() {
*/ */
var ngListDirective = function() { var ngListDirective = function() {
return { return {
restrict: 'A',
require: 'ngModel', require: 'ngModel',
link: function(scope, element, attr, ctrl) { link: function(scope, element, attr, ctrl) {
// We want to control whitespace trimming so we use this convoluted approach // We want to control whitespace trimming so we use this convoluted approach
@@ -2526,6 +2533,7 @@ var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
*/ */
var ngValueDirective = function() { var ngValueDirective = function() {
return { return {
restrict: 'A',
priority: 100, priority: 100,
compile: function(tpl, tplAttr) { compile: function(tpl, tplAttr) {
if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) {
@@ -2688,6 +2696,7 @@ var ngValueDirective = function() {
*/ */
var ngModelOptionsDirective = function() { var ngModelOptionsDirective = function() {
return { return {
restrict: 'A',
controller: ['$scope', '$attrs', function($scope, $attrs) { controller: ['$scope', '$attrs', function($scope, $attrs) {
var that = this; var that = this;
this.$options = $scope.$eval($attrs.ngModelOptions); this.$options = $scope.$eval($attrs.ngModelOptions);

View File

@@ -178,6 +178,7 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
*/ */
var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) {
return { return {
restrict: 'A',
compile: function (tElement, tAttrs) { compile: function (tElement, tAttrs) {
tElement.addClass('ng-binding'); tElement.addClass('ng-binding');

View File

@@ -221,6 +221,7 @@
*/ */
var ngControllerDirective = [function() { var ngControllerDirective = [function() {
return { return {
restrict: 'A',
scope: true, scope: true,
controller: '@', controller: '@',
priority: 500 priority: 500

View File

@@ -43,6 +43,7 @@ forEach(
var directiveName = directiveNormalize('ng-' + name); var directiveName = directiveNormalize('ng-' + name);
ngEventDirectives[directiveName] = ['$parse', function($parse) { ngEventDirectives[directiveName] = ['$parse', function($parse) {
return { return {
restrict: 'A',
compile: function($element, attr) { compile: function($element, attr) {
var fn = $parse(attr[directiveName]); var fn = $parse(attr[directiveName]);
return function ngEventHandler(scope, element) { return function ngEventHandler(scope, element) {

View File

@@ -212,6 +212,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
var NG_REMOVED = '$$NG_REMOVED'; var NG_REMOVED = '$$NG_REMOVED';
var ngRepeatMinErr = minErr('ngRepeat'); var ngRepeatMinErr = minErr('ngRepeat');
return { return {
restrict: 'A',
multiElement: true, multiElement: true,
transclude: 'element', transclude: 'element',
priority: 1000, priority: 1000,

View File

@@ -157,6 +157,7 @@
*/ */
var ngShowDirective = ['$animate', function($animate) { var ngShowDirective = ['$animate', function($animate) {
return { return {
restrict: 'A',
multiElement: true, multiElement: true,
link: function(scope, element, attr) { link: function(scope, element, attr) {
scope.$watch(attr.ngShow, function ngShowWatchAction(value){ scope.$watch(attr.ngShow, function ngShowWatchAction(value){
@@ -311,6 +312,7 @@ var ngShowDirective = ['$animate', function($animate) {
*/ */
var ngHideDirective = ['$animate', function($animate) { var ngHideDirective = ['$animate', function($animate) {
return { return {
restrict: 'A',
multiElement: true, multiElement: true,
link: function(scope, element, attr) { link: function(scope, element, attr) {
scope.$watch(attr.ngHide, function ngHideWatchAction(value){ scope.$watch(attr.ngHide, function ngHideWatchAction(value){

View File

@@ -135,7 +135,11 @@ var ngOptionsMinErr = minErr('ngOptions');
</example> </example>
*/ */
var ngOptionsDirective = valueFn({ terminal: true }); var ngOptionsDirective = valueFn({
restrict: 'A',
terminal: true
});
// jshint maxlen: false // jshint maxlen: false
var selectDirective = ['$compile', '$parse', function($compile, $parse) { var selectDirective = ['$compile', '$parse', function($compile, $parse) {
//000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888

View File

@@ -255,14 +255,6 @@ describe('$compile', function() {
})); }));
it('should allow directives in comments', inject(
function($compile, $rootScope, log) {
element = $compile('<div>0<!-- directive: log angular -->1</div>')($rootScope);
expect(log).toEqual('angular');
}
));
it('should receive scope, element, and attributes', function() { it('should receive scope, element, and attributes', function() {
var injector; var injector;
module(function() { module(function() {
@@ -437,20 +429,21 @@ describe('$compile', function() {
describe('restrict', function() { describe('restrict', function() {
it('should allow restriction of attributes', function() { it('should allow restriction of availability', function () {
module(function() { module(function () {
forEach({div:'E', attr:'A', clazz:'C', all:'EAC'}, function(restrict, name) { forEach({div: 'E', attr: 'A', clazz: 'C', comment: 'M', all: 'EACM'},
directive(name, function(log) { function (restrict, name) {
directive(name, function (log) {
return { return {
restrict: restrict, restrict: restrict,
compile: valueFn(function(scope, element, attr) { compile: valueFn(function (scope, element, attr) {
log(name); log(name);
}) })
}; };
}); });
}); });
}); });
inject(function($rootScope, $compile, log) { inject(function ($rootScope, $compile, log) {
dealoc($compile('<span div class="div"></span>')($rootScope)); dealoc($compile('<span div class="div"></span>')($rootScope));
expect(log).toEqual(''); expect(log).toEqual('');
log.reset(); log.reset();
@@ -459,7 +452,7 @@ describe('$compile', function() {
expect(log).toEqual('div'); expect(log).toEqual('div');
log.reset(); log.reset();
dealoc($compile('<attr class=""attr"></attr>')($rootScope)); dealoc($compile('<attr class="attr"></attr>')($rootScope));
expect(log).toEqual(''); expect(log).toEqual('');
log.reset(); log.reset();
@@ -475,8 +468,38 @@ describe('$compile', function() {
expect(log).toEqual('clazz'); expect(log).toEqual('clazz');
log.reset(); log.reset();
dealoc($compile('<all class="all" all></all>')($rootScope)); dealoc($compile('<!-- directive: comment -->')($rootScope));
expect(log).toEqual('all; all; all'); expect(log).toEqual('comment');
log.reset();
dealoc($compile('<all class="all" all><!-- directive: all --></all>')($rootScope));
expect(log).toEqual('all; all; all; all');
});
});
it('should use EA rule as the default', function () {
module(function () {
directive('defaultDir', function (log) {
return {
compile: function () {
log('defaultDir');
}
};
});
});
inject(function ($rootScope, $compile, log) {
dealoc($compile('<span default-dir ></span>')($rootScope));
expect(log).toEqual('defaultDir');
log.reset();
dealoc($compile('<default-dir></default-dir>')($rootScope));
expect(log).toEqual('defaultDir');
log.reset();
dealoc($compile('<span class="default-dir"></span>')($rootScope));
expect(log).toEqual('');
log.reset();
}); });
}); });
}); });