feat($compile): support omitting required controller name if same as the local name

Basically, making `require: {someDir: '?^someDir'}` equivalent to `require: {someDir: '?^'}`.

Closes #14513
This commit is contained in:
Georgios Kalpakas
2016-04-25 23:43:38 +03:00
parent 6d2e0718d2
commit 0780666e41
2 changed files with 125 additions and 2 deletions
+17 -2
View File
@@ -324,8 +324,9 @@
* If the `require` property is an object and `bindToController` is truthy, then the required controllers are
* bound to the controller using the keys of the `require` property. This binding occurs after all the controllers
* have been constructed but before `$onInit` is called.
* If the name of the required controller is the same as the local name (the key), the name can be
* omitted. For example, `{parentDir: '^^'}` is equivalent to `{parentDir: '^^parentDir'}`.
* See the {@link $compileProvider#component} helper for an example of how this can be used.
*
* If no such required directive(s) can be found, or if the directive does not have a controller, then an error is
* raised (unless no link function is specified and the required controllers are not being bound to the directive
* controller, in which case error checking is skipped). The name can be prefixed with:
@@ -954,6 +955,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
}
function getDirectiveRequire(directive) {
var require = directive.require || (directive.controller && directive.name);
if (!isArray(require) && isObject(require)) {
forEach(require, function(value, key) {
var match = value.match(REQUIRE_PREFIX_REGEXP);
var name = value.substring(match[0].length);
if (!name) require[key] = match[0] + key;
});
}
return require;
}
/**
* @ngdoc method
* @name $compileProvider#directive
@@ -990,7 +1005,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
directive.priority = directive.priority || 0;
directive.index = index;
directive.name = directive.name || name;
directive.require = directive.require || (directive.controller && directive.name);
directive.require = getDirectiveRequire(directive);
directive.restrict = directive.restrict || 'EA';
directive.$$moduleName = directiveFactory.$$moduleName;
directives.push(directive);
+108
View File
@@ -6268,6 +6268,89 @@ describe('$compile', function() {
});
});
it('should use the key if the name of a required controller is omitted', function() {
function ParentController() { this.name = 'Parent'; }
function ParentOptController() { this.name = 'ParentOpt'; }
function ParentOrSiblingController() { this.name = 'ParentOrSibling'; }
function ParentOrSiblingOptController() { this.name = 'ParentOrSiblingOpt'; }
function SiblingController() { this.name = 'Sibling'; }
function SiblingOptController() { this.name = 'SiblingOpt'; }
angular.module('my', [])
.component('me', {
require: {
parent: '^^',
parentOpt: '?^^',
parentOrSibling1: '^',
parentOrSiblingOpt1: '?^',
parentOrSibling2: '^',
parentOrSiblingOpt2: '?^',
sibling: '',
siblingOpt: '?'
}
})
.directive('parent', function() {
return {controller: ParentController};
})
.directive('parentOpt', function() {
return {controller: ParentOptController};
})
.directive('parentOrSibling1', function() {
return {controller: ParentOrSiblingController};
})
.directive('parentOrSiblingOpt1', function() {
return {controller: ParentOrSiblingOptController};
})
.directive('parentOrSibling2', function() {
return {controller: ParentOrSiblingController};
})
.directive('parentOrSiblingOpt2', function() {
return {controller: ParentOrSiblingOptController};
})
.directive('sibling', function() {
return {controller: SiblingController};
})
.directive('siblingOpt', function() {
return {controller: SiblingOptController};
});
module('my');
inject(function($compile, $rootScope) {
var template =
'<div>' +
// With optional
'<parent parent-opt parent-or-sibling-1 parent-or-sibling-opt-1>' +
'<me parent-or-sibling-2 parent-or-sibling-opt-2 sibling sibling-opt></me>' +
'</parent>' +
// Without optional
'<parent parent-or-sibling-1>' +
'<me parent-or-sibling-2 sibling></me>' +
'</parent>' +
'</div>';
element = $compile(template)($rootScope);
var ctrl1 = element.find('me').eq(0).controller('me');
expect(ctrl1.parent).toEqual(jasmine.any(ParentController));
expect(ctrl1.parentOpt).toEqual(jasmine.any(ParentOptController));
expect(ctrl1.parentOrSibling1).toEqual(jasmine.any(ParentOrSiblingController));
expect(ctrl1.parentOrSiblingOpt1).toEqual(jasmine.any(ParentOrSiblingOptController));
expect(ctrl1.parentOrSibling2).toEqual(jasmine.any(ParentOrSiblingController));
expect(ctrl1.parentOrSiblingOpt2).toEqual(jasmine.any(ParentOrSiblingOptController));
expect(ctrl1.sibling).toEqual(jasmine.any(SiblingController));
expect(ctrl1.siblingOpt).toEqual(jasmine.any(SiblingOptController));
var ctrl2 = element.find('me').eq(1).controller('me');
expect(ctrl2.parent).toEqual(jasmine.any(ParentController));
expect(ctrl2.parentOpt).toBe(null);
expect(ctrl2.parentOrSibling1).toEqual(jasmine.any(ParentOrSiblingController));
expect(ctrl2.parentOrSiblingOpt1).toBe(null);
expect(ctrl2.parentOrSibling2).toEqual(jasmine.any(ParentOrSiblingController));
expect(ctrl2.parentOrSiblingOpt2).toBe(null);
expect(ctrl2.sibling).toEqual(jasmine.any(SiblingController));
expect(ctrl2.siblingOpt).toBe(null);
});
});
it('should not bind required controllers if bindToController is falsy', function() {
var parentController, siblingController;
@@ -6797,6 +6880,31 @@ describe('$compile', function() {
});
});
it('should support omitting the name of the required controller if it is the same as the key',
function() {
module(function() {
directive('myC1', valueFn({
controller: function() { this.name = 'c1'; }
}));
directive('myC2', valueFn({
controller: function() { this.name = 'c2'; }
}));
directive('dep', function(log) {
return {
require: { myC1: '^', myC2: '^' },
link: function(scope, element, attrs, controllers) {
log('dep:' + controllers.myC1.name + '-' + controllers.myC2.name);
}
};
});
});
inject(function(log, $compile, $rootScope) {
element = $compile('<div my-c1 my-c2><div dep></div></div>')($rootScope);
expect(log).toEqual('dep:c1-c2');
});
}
);
it('should instantiate the controller just once when template/templateUrl', function() {
var syncCtrlSpy = jasmine.createSpy('sync controller'),
asyncCtrlSpy = jasmine.createSpy('async controller');