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
* 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>`
* * `C` - Class: `<div class="my-directive: exp;"></div>`
* * `M` - Comment: `<!-- directive: my-directive exp -->`
@@ -581,7 +581,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
directive.index = index;
directive.name = directive.name || name;
directive.require = directive.require || (directive.controller && directive.name);
directive.restrict = directive.restrict || 'A';
directive.restrict = directive.restrict || 'EA';
directives.push(directive);
} catch (e) {
$exceptionHandler(e);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -135,7 +135,11 @@ var ngOptionsMinErr = minErr('ngOptions');
</example>
*/
var ngOptionsDirective = valueFn({ terminal: true });
var ngOptionsDirective = valueFn({
restrict: 'A',
terminal: true
});
// jshint maxlen: false
var selectDirective = ['$compile', '$parse', function($compile, $parse) {
//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() {
var injector;
module(function() {
@@ -437,20 +429,21 @@ describe('$compile', function() {
describe('restrict', function() {
it('should allow restriction of attributes', function() {
module(function() {
forEach({div:'E', attr:'A', clazz:'C', all:'EAC'}, function(restrict, name) {
directive(name, function(log) {
it('should allow restriction of availability', function () {
module(function () {
forEach({div: 'E', attr: 'A', clazz: 'C', comment: 'M', all: 'EACM'},
function (restrict, name) {
directive(name, function (log) {
return {
restrict: restrict,
compile: valueFn(function(scope, element, attr) {
compile: valueFn(function (scope, element, attr) {
log(name);
})
};
});
});
});
inject(function($rootScope, $compile, log) {
inject(function ($rootScope, $compile, log) {
dealoc($compile('<span div class="div"></span>')($rootScope));
expect(log).toEqual('');
log.reset();
@@ -459,7 +452,7 @@ describe('$compile', function() {
expect(log).toEqual('div');
log.reset();
dealoc($compile('<attr class=""attr"></attr>')($rootScope));
dealoc($compile('<attr class="attr"></attr>')($rootScope));
expect(log).toEqual('');
log.reset();
@@ -475,8 +468,38 @@ describe('$compile', function() {
expect(log).toEqual('clazz');
log.reset();
dealoc($compile('<all class="all" all></all>')($rootScope));
expect(log).toEqual('all; all; all');
dealoc($compile('<!-- directive: comment -->')($rootScope));
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();
});
});
});