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

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
108 changes: 108 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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');
Expand Down