aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTobias Bosch2013-11-14 13:50:36 -0800
committerVojta Jina2013-11-14 20:59:50 -0800
commit90f87072e83234ae366cfeb3c281503c31dad738 (patch)
treed969b74c0fe993900bc91e3e9f1d8004d238ac2c /src
parentc785918cbd245cc8ecf9a38e373b121c4e68a55b (diff)
downloadangular.js-90f87072e83234ae366cfeb3c281503c31dad738.tar.bz2
fix($compile): accessing controllers of transcluded directives from children
Additional API (backwards compatible) - Injects `$transclude` (see directive controllers) as 5th argument to directive link functions. - `$transclude` takes an optional scope as first parameter that overrides the bound scope. Deprecations: - `transclude` parameter of directive compile functions (use the new parameter for link functions instead). Refactorings: - Don't use comment node to temporarily store controllers - `ngIf`, `ngRepeat`, ... now all use `$transclude` Closes #4935.
Diffstat (limited to 'src')
-rw-r--r--src/ng/compile.js141
-rw-r--r--src/ng/directive/ngIf.js6
-rw-r--r--src/ng/directive/ngInclude.js6
-rw-r--r--src/ng/directive/ngRepeat.js6
-rw-r--r--src/ng/directive/ngSwitch.js16
-rw-r--r--src/ngRoute/directive/ngView.js6
6 files changed, 110 insertions, 71 deletions
diff --git a/src/ng/compile.js b/src/ng/compile.js
index 039c211c..4d83f379 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -178,8 +178,9 @@
* * `$scope` - Current scope associated with the element
* * `$element` - Current element
* * `$attrs` - Current attributes object for the element
- * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope:
- * `function(cloneLinkingFn)`.
+ * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope.
+ * The scope can be overridden by an optional first argument.
+ * `function([scope], cloneLinkingFn)`.
*
*
* #### `require`
@@ -272,7 +273,7 @@
* * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared
* between all directive compile functions.
*
- * * `transclude` - A transclude linking function: `function(scope, cloneLinkingFn)`.
+ * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)`
*
* <div class="alert alert-warning">
* **Note:** The template instance and the link instance may be different objects if the template has
@@ -281,6 +282,12 @@
* should be done in a linking function rather than in a compile function.
* </div>
*
+ * <div class="alert alert-error">
+ * **Note:** The `transclude` function that is passed to the compile function is deperecated, as it
+ * e.g. does not know about the right outer scope. Please use the transclude function that is passed
+ * to the link function instead.
+ * </div>
+
* A compile function can have a return value which can be either a function or an object.
*
* * returning a (post-link) function - is equivalent to registering the linking function via the
@@ -295,7 +302,7 @@
* This property is used only if the `compile` property is not defined.
*
* <pre>
- * function link(scope, iElement, iAttrs, controller) { ... }
+ * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
* </pre>
*
* The link function is responsible for registering DOM listeners as well as updating the DOM. It is
@@ -316,6 +323,10 @@
* element defines a controller. The controller is shared among all the directives, which allows
* the directives to use the controllers as a communication channel.
*
+ * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
+ * The scope can be overridden by an optional first argument. This is the same as the `$transclude`
+ * parameter of directive controllers.
+ * `function([scope], cloneLinkingFn)`.
*
*
* #### Pre-linking function
@@ -821,7 +832,7 @@ function $CompileProvider($provide) {
var compositeLinkFn =
compileNodes($compileNodes, transcludeFn, $compileNodes,
maxPriority, ignoreDirective, previousCompileContext);
- return function publicLinkFn(scope, cloneConnectFn){
+ return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){
assertArg(scope, 'scope');
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
// and sometimes changes the structure of the DOM.
@@ -829,6 +840,10 @@ function $CompileProvider($provide) {
? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
: $compileNodes;
+ forEach(transcludeControllers, function(instance, name) {
+ $linkNode.data('$' + name + 'Controller', instance);
+ });
+
// Attach scope only to non-text nodes.
for(var i = 0, ii = $linkNode.length; i<ii; i++) {
var node = $linkNode[i];
@@ -940,12 +955,19 @@ function $CompileProvider($provide) {
}
function createBoundTranscludeFn(scope, transcludeFn) {
- return function boundTranscludeFn(cloneFn) {
- var transcludedScope = scope.$new(),
- clone;
- transcludedScope.$$transcluded = true;
- clone = transcludeFn(transcludedScope, cloneFn);
- clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy));
+ return function boundTranscludeFn(transcludedScope, cloneFn, controllers) {
+ var scopeCreated = false;
+
+ if (!transcludedScope) {
+ transcludedScope = scope.$new();
+ transcludedScope.$$transcluded = true;
+ scopeCreated = true;
+ }
+
+ var clone = transcludeFn(transcludedScope, cloneFn, controllers);
+ if (scopeCreated) {
+ clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy));
+ }
return clone;
};
}
@@ -1086,9 +1108,9 @@ function $CompileProvider($provide) {
* @returns {Function}
*/
function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) {
- return function(scope, element, attrs, controllers) {
+ return function(scope, element, attrs, controllers, transcludeFn) {
element = groupScan(element[0], attrStart, attrEnd);
- return linkFn(scope, element, attrs, controllers);
+ return linkFn(scope, element, attrs, controllers, transcludeFn);
};
}
@@ -1125,7 +1147,9 @@ function $CompileProvider($provide) {
controllerDirectives = previousCompileContext.controllerDirectives,
newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
templateDirective = previousCompileContext.templateDirective,
- transcludeDirective = previousCompileContext.transcludeDirective,
+ nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
+ hasTranscludeDirective = false,
+ hasElementTranscludeDirective = false,
$compileNode = templateAttrs.$$element = jqLite(compileNode),
directive,
directiveName,
@@ -1176,15 +1200,18 @@ function $CompileProvider($provide) {
}
if (directiveValue = directive.transclude) {
+ hasTranscludeDirective = true;
+
// Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion.
// This option should only be used by directives that know how to how to safely handle element transclusion,
// where the transcluded nodes are added or replaced after linking.
if (!directive.$$tlb) {
- assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
- transcludeDirective = directive;
+ assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
+ nonTlbTranscludeDirective = directive;
}
if (directiveValue == 'element') {
+ hasElementTranscludeDirective = true;
terminalPriority = directive.priority;
$template = groupScan(compileNode, attrStart, attrEnd);
$compileNode = templateAttrs.$$element =
@@ -1200,9 +1227,9 @@ function $CompileProvider($provide) {
// - newIsolateScopeDirective or templateDirective - combining templates with
// element transclusion doesn't make sense.
//
- // We need only transcludeDirective so that we prevent putting transclusion
+ // We need only nonTlbTranscludeDirective so that we prevent putting transclusion
// on the same element more than once.
- transcludeDirective: transcludeDirective
+ nonTlbTranscludeDirective: nonTlbTranscludeDirective
});
} else {
$template = jqLite(jqLiteClone(compileNode)).contents();
@@ -1271,7 +1298,7 @@ function $CompileProvider($provide) {
controllerDirectives: controllerDirectives,
newIsolateScopeDirective: newIsolateScopeDirective,
templateDirective: templateDirective,
- transcludeDirective: transcludeDirective
+ nonTlbTranscludeDirective: nonTlbTranscludeDirective
});
ii = directives.length;
} else if (directive.compile) {
@@ -1295,7 +1322,7 @@ function $CompileProvider($provide) {
}
nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
- nodeLinkFn.transclude = transcludeDirective && childTranscludeFn;
+ nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn;
// might be normal or delayed nodeLinkFn depending on if templateUrl is present
return nodeLinkFn;
@@ -1322,7 +1349,7 @@ function $CompileProvider($provide) {
}
- function getControllers(require, $element) {
+ function getControllers(require, $element, elementControllers) {
var value, retrievalMethod = 'data', optional = false;
if (isString(require)) {
while((value = require.charAt(0)) == '^' || value == '?') {
@@ -1332,13 +1359,12 @@ function $CompileProvider($provide) {
}
optional = optional || value == '?';
}
+ value = null;
- value = $element[retrievalMethod]('$' + require + 'Controller');
-
- if ($element[0].nodeType == 8 && $element[0].$$controller) { // Transclusion comment node
- value = value || $element[0].$$controller;
- $element[0].$$controller = null;
+ if (elementControllers && retrievalMethod === 'data') {
+ value = elementControllers[require];
}
+ value = value || $element[retrievalMethod]('$' + require + 'Controller');
if (!value && !optional) {
throw $compileMinErr('ctreq',
@@ -1349,7 +1375,7 @@ function $CompileProvider($provide) {
} else if (isArray(require)) {
value = [];
forEach(require, function(require) {
- value.push(getControllers(require, $element));
+ value.push(getControllers(require, $element, elementControllers));
});
}
return value;
@@ -1357,7 +1383,7 @@ function $CompileProvider($provide) {
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
- var attrs, $element, i, ii, linkFn, controller, isolateScope;
+ var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn;
if (compileNode === linkNode) {
attrs = templateAttrs;
@@ -1451,14 +1477,14 @@ function $CompileProvider($provide) {
}
});
}
-
+ transcludeFn = boundTranscludeFn && controllersBoundTransclude;
if (controllerDirectives) {
forEach(controllerDirectives, function(directive) {
var locals = {
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
$element: $element,
$attrs: attrs,
- $transclude: boundTranscludeFn
+ $transclude: transcludeFn
}, controllerInstance;
controller = directive.controller;
@@ -1467,16 +1493,16 @@ function $CompileProvider($provide) {
}
controllerInstance = $controller(controller, locals);
-
- // Directives with element transclusion and a controller need to attach controller
- // to the comment node created by the compiler, but jQuery .data doesn't support
- // attaching data to comment nodes so instead we set it directly on the element and
- // remove it after we read it later.
- if ($element[0].nodeType == 8) { // Transclusion comment node
- $element[0].$$controller = controllerInstance;
- } else {
+ // For directives with element transclusion the element is a comment,
+ // but jQuery .data doesn't support attaching data to comment nodes as it's hard to
+ // clean up (http://bugs.jquery.com/ticket/8335).
+ // Instead, we save the controllers for the element in a local hash and attach to .data
+ // later, once we have the actual element.
+ elementControllers[directive.name] = controllerInstance;
+ if (!hasElementTranscludeDirective) {
$element.data('$' + directive.name + 'Controller', controllerInstance);
}
+
if (directive.controllerAs) {
locals.$scope[directive.controllerAs] = controllerInstance;
}
@@ -1488,7 +1514,7 @@ function $CompileProvider($provide) {
try {
linkFn = preLinkFns[i];
linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
- linkFn.require && getControllers(linkFn.require, $element));
+ linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn);
} catch (e) {
$exceptionHandler(e, startingTag($element));
}
@@ -1508,11 +1534,28 @@ function $CompileProvider($provide) {
try {
linkFn = postLinkFns[i];
linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
- linkFn.require && getControllers(linkFn.require, $element));
+ linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn);
} catch (e) {
$exceptionHandler(e, startingTag($element));
}
}
+
+ // This is the function that is injected as `$transclude`.
+ function controllersBoundTransclude(scope, cloneAttachFn) {
+ var transcludeControllers;
+
+ // no scope passed
+ if (arguments.length < 2) {
+ cloneAttachFn = scope;
+ scope = undefined;
+ }
+
+ if (hasElementTranscludeDirective) {
+ transcludeControllers = elementControllers;
+ }
+
+ return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers);
+ }
}
}
@@ -1622,7 +1665,7 @@ function $CompileProvider($provide) {
$http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}).
success(function(content) {
- var compileNode, tempTemplateAttrs, $template;
+ var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
content = denormalizeTemplate(content);
@@ -1667,7 +1710,7 @@ function $CompileProvider($provide) {
var scope = linkQueue.shift(),
beforeTemplateLinkNode = linkQueue.shift(),
linkRootElement = linkQueue.shift(),
- controller = linkQueue.shift(),
+ boundTranscludeFn = linkQueue.shift(),
linkNode = $compileNode[0];
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
@@ -1675,9 +1718,13 @@ function $CompileProvider($provide) {
linkNode = jqLiteClone(compileNode);
replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
}
-
+ if (afterTemplateNodeLinkFn.transclude) {
+ childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude);
+ } else {
+ childBoundTranscludeFn = boundTranscludeFn;
+ }
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
- controller);
+ childBoundTranscludeFn);
}
linkQueue = null;
}).
@@ -1685,14 +1732,14 @@ function $CompileProvider($provide) {
throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url);
});
- return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
+ return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
if (linkQueue) {
linkQueue.push(scope);
linkQueue.push(node);
linkQueue.push(rootElement);
- linkQueue.push(controller);
+ linkQueue.push(boundTranscludeFn);
} else {
- afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
+ afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn);
}
};
}
diff --git a/src/ng/directive/ngIf.js b/src/ng/directive/ngIf.js
index 6e845e28..dcb3825d 100644
--- a/src/ng/directive/ngIf.js
+++ b/src/ng/directive/ngIf.js
@@ -86,15 +86,14 @@ var ngIfDirective = ['$animate', function($animate) {
terminal: true,
restrict: 'A',
$$tlb: true,
- compile: function (element, attr, transclude) {
- return function ($scope, $element, $attr) {
+ link: function ($scope, $element, $attr, ctrl, $transclude) {
var block, childScope;
$scope.$watch($attr.ngIf, function ngIfWatchAction(value) {
if (toBoolean(value)) {
if (!childScope) {
childScope = $scope.$new();
- transclude(childScope, function (clone) {
+ $transclude(childScope, function (clone) {
block = {
startNode: clone[0],
endNode: clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ')
@@ -115,7 +114,6 @@ var ngIfDirective = ['$animate', function($animate) {
}
}
});
- };
}
};
}];
diff --git a/src/ng/directive/ngInclude.js b/src/ng/directive/ngInclude.js
index 7ca9a48b..7f395f96 100644
--- a/src/ng/directive/ngInclude.js
+++ b/src/ng/directive/ngInclude.js
@@ -154,12 +154,12 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
priority: 400,
terminal: true,
transclude: 'element',
- compile: function(element, attr, transclusion) {
+ compile: function(element, attr) {
var srcExp = attr.ngInclude || attr.src,
onloadExp = attr.onload || '',
autoScrollExp = attr.autoscroll;
- return function(scope, $element) {
+ return function(scope, $element, $attr, ctrl, $transclude) {
var changeCounter = 0,
currentScope,
currentElement;
@@ -188,7 +188,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
if (thisChangeId !== changeCounter) return;
var newScope = scope.$new();
- transclusion(newScope, function(clone) {
+ $transclude(newScope, function(clone) {
cleanupLastIncludeContent();
currentScope = newScope;
diff --git a/src/ng/directive/ngRepeat.js b/src/ng/directive/ngRepeat.js
index c7d0c005..a5614bf4 100644
--- a/src/ng/directive/ngRepeat.js
+++ b/src/ng/directive/ngRepeat.js
@@ -201,8 +201,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
priority: 1000,
terminal: true,
$$tlb: true,
- compile: function(element, attr, linker) {
- return function($scope, $element, $attr){
+ link: function($scope, $element, $attr, ctrl, $transclude){
var expression = $attr.ngRepeat;
var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn,
@@ -364,7 +363,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
// jshint bitwise: true
if (!block.startNode) {
- linker(childScope, function(clone) {
+ $transclude(childScope, function(clone) {
clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' ');
$animate.enter(clone, null, jqLite(previousNode));
previousNode = clone;
@@ -377,7 +376,6 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
}
lastBlockMap = nextBlockMap;
});
- };
}
};
}];
diff --git a/src/ng/directive/ngSwitch.js b/src/ng/directive/ngSwitch.js
index 11ef7b71..d4387ae2 100644
--- a/src/ng/directive/ngSwitch.js
+++ b/src/ng/directive/ngSwitch.js
@@ -160,10 +160,10 @@ var ngSwitchWhenDirective = ngDirective({
transclude: 'element',
priority: 800,
require: '^ngSwitch',
- compile: function(element, attrs, transclude) {
- return function(scope, element, attr, ctrl) {
+ compile: function(element, attrs) {
+ return function(scope, element, attr, ctrl, $transclude) {
ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
- ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: transclude, element: element });
+ ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
};
}
});
@@ -172,10 +172,8 @@ var ngSwitchDefaultDirective = ngDirective({
transclude: 'element',
priority: 800,
require: '^ngSwitch',
- compile: function(element, attrs, transclude) {
- return function(scope, element, attr, ctrl) {
- ctrl.cases['?'] = (ctrl.cases['?'] || []);
- ctrl.cases['?'].push({ transclude: transclude, element: element });
- };
- }
+ link: function(scope, element, attr, ctrl, $transclude) {
+ ctrl.cases['?'] = (ctrl.cases['?'] || []);
+ ctrl.cases['?'].push({ transclude: $transclude, element: element });
+ }
});
diff --git a/src/ngRoute/directive/ngView.js b/src/ngRoute/directive/ngView.js
index a8a136df..3271ac0c 100644
--- a/src/ngRoute/directive/ngView.js
+++ b/src/ngRoute/directive/ngView.js
@@ -173,8 +173,7 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
terminal: true,
priority: 400,
transclude: 'element',
- compile: function(element, attr, linker) {
- return function(scope, $element, attr) {
+ link: function(scope, $element, attr, ctrl, $transclude) {
var currentScope,
currentElement,
autoScrollExp = attr.autoscroll,
@@ -200,7 +199,7 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
if (template) {
var newScope = scope.$new();
- linker(newScope, function(clone) {
+ $transclude(newScope, function(clone) {
clone.html(template);
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
if (angular.isDefined(autoScrollExp)
@@ -235,7 +234,6 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
cleanupLastView();
}
}
- };
}
};
}