aboutsummaryrefslogtreecommitdiffstats
path: root/src/ng/compile.js
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/ng/compile.js
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/ng/compile.js')
-rw-r--r--src/ng/compile.js141
1 files changed, 94 insertions, 47 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);
}
};
}