Skip to content

Commit 4f81d5f

Browse files
Caitlin Pottercaitp
Caitlin Potter
authored andcommitted
feat($compile): optionally get controllers from ancestors only
Implement option to strengthen require '^' operator, by adding another '^'. When a second '^' is used, the controller will only search parent nodes for the matching controller, and will throw or return null if not found, depending on whether or not the requirement is optional. Closes angular#4518
1 parent 4b83f6c commit 4f81d5f

File tree

2 files changed

+61
-8
lines changed

2 files changed

+61
-8
lines changed

src/ng/compile.js

+24-8
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,11 @@
212212
* * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
213213
* * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
214214
* * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
215+
* * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
215216
* * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
216217
* `null` to the `link` fn if not found.
218+
* * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
219+
* `null` to the `link` fn if not found.
217220
*
218221
*
219222
* #### `controllerAs`
@@ -567,7 +570,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
567570
Suffix = 'Directive',
568571
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,
569572
CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/,
570-
ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset');
573+
ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
574+
REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
571575

572576
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
573577
// The assumption is that future DOM event attribute names will begin with
@@ -1589,22 +1593,34 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
15891593

15901594
function getControllers(directiveName, require, $element, elementControllers) {
15911595
var value, retrievalMethod = 'data', optional = false;
1596+
var $searchElement = $element;
1597+
var match;
15921598
if (isString(require)) {
1593-
while((value = require.charAt(0)) == '^' || value == '?') {
1594-
require = require.substr(1);
1595-
if (value == '^') {
1596-
retrievalMethod = 'inheritedData';
1597-
}
1598-
optional = optional || value == '?';
1599+
match = require.match(REQUIRE_PREFIX_REGEXP);
1600+
require = require.substring(match[0].length);
1601+
1602+
if (match[3]) {
1603+
if (match[1]) match[3] = null;
1604+
else match[1] = match[3];
15991605
}
1606+
if (match[1] === '^') {
1607+
retrievalMethod = 'inheritedData';
1608+
} else if (match[1] === '^^') {
1609+
retrievalMethod = 'inheritedData';
1610+
$searchElement = $element.parent();
1611+
}
1612+
if (match[2] === '?') {
1613+
optional = true;
1614+
}
1615+
16001616
value = null;
16011617

16021618
if (elementControllers && retrievalMethod === 'data') {
16031619
if (value = elementControllers[require]) {
16041620
value = value.instance;
16051621
}
16061622
}
1607-
value = value || $element[retrievalMethod]('$' + require + 'Controller');
1623+
value = value || $searchElement[retrievalMethod]('$' + require + 'Controller');
16081624

16091625
if (!value && !optional) {
16101626
throw $compileMinErr('ctreq',

test/ng/compileSpec.js

+37
Original file line numberDiff line numberDiff line change
@@ -3672,6 +3672,43 @@ describe('$compile', function() {
36723672
});
36733673

36743674

3675+
it('should get required parent controller', function() {
3676+
module(function() {
3677+
directive('nested', function(log) {
3678+
return {
3679+
require: '^^?nested',
3680+
controller: function($scope) {},
3681+
link: function(scope, element, attrs, controller) {
3682+
log(!!controller);
3683+
}
3684+
};
3685+
});
3686+
});
3687+
inject(function(log, $compile, $rootScope) {
3688+
element = $compile('<div nested><div nested></div></div>')($rootScope);
3689+
expect(log).toEqual('true; false');
3690+
});
3691+
});
3692+
3693+
3694+
it('should throw if required parent is not found', function() {
3695+
module(function() {
3696+
directive('nested', function() {
3697+
return {
3698+
require: '^^nested',
3699+
controller: function($scope) {},
3700+
link: function(scope, element, attrs, controller) {}
3701+
};
3702+
});
3703+
});
3704+
inject(function($compile, $rootScope) {
3705+
expect(function() {
3706+
element = $compile('<div nested></div>')($rootScope);
3707+
}).toThrowMinErr('$compile', 'ctreq', "Controller 'nested', required by directive 'nested', can't be found!");
3708+
});
3709+
});
3710+
3711+
36753712
it('should get required controller via linkingFn (template)', function() {
36763713
module(function() {
36773714
directive('dirA', function() {

0 commit comments

Comments
 (0)