aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/ng/compile.js50
-rwxr-xr-xtest/ng/compileSpec.js84
-rw-r--r--test/ng/directive/ngRepeatSpec.js37
3 files changed, 142 insertions, 29 deletions
diff --git a/src/ng/compile.js b/src/ng/compile.js
index a13be3b8..c44985fd 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -469,7 +469,7 @@ function $CompileProvider($provide) {
//================================
- function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective) {
+ function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) {
if (!($compileNodes instanceof jqLite)) {
// jquery always rewraps, whereas we need to preserve the original selector so that we can modify it.
$compileNodes = jqLite($compileNodes);
@@ -481,7 +481,7 @@ function $CompileProvider($provide) {
$compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0];
}
});
- var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective);
+ var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority, ignoreDirective, previousCompileContext);
return function publicLinkFn(scope, cloneConnectFn){
assertArg(scope, 'scope');
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
@@ -528,7 +528,7 @@ function $CompileProvider($provide) {
* @param {number=} max directive priority
* @returns {?function} A composite linking function of all of the matched directives or null.
*/
- function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective) {
+ function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, previousCompileContext) {
var linkFns = [],
nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
@@ -539,7 +539,7 @@ function $CompileProvider($provide) {
directives = collectDirectives(nodeList[i], [], attrs, i == 0 ? maxPriority : undefined, ignoreDirective);
nodeLinkFn = (directives.length)
- ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, null, [], [])
+ ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, null, [], [], previousCompileContext)
: null;
childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes || !nodeList[i].childNodes.length)
@@ -550,6 +550,7 @@ function $CompileProvider($provide) {
linkFns.push(nodeLinkFn);
linkFns.push(childLinkFn);
linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
+ previousCompileContext = null; //use the previous context only for the first element in the virtual group
}
// return a linking function if we have found anything, null otherwise
@@ -750,23 +751,26 @@ function $CompileProvider($provide) {
* scope argument is auto-generated to the new child of the transcluded parent scope.
* @param {JQLite} jqCollection If we are working on the root of the compile tree then this
* argument has the root jqLite array so that we can replace nodes on it.
- * @param {Object=} ignoreDirective An optional directive that will be ignored when compiling
+ * @param {Object=} originalReplaceDirective An optional directive that will be ignored when compiling
* the transclusion.
* @param {Array.<Function>} preLinkFns
* @param {Array.<Function>} postLinkFns
+ * @param {Object} previousCompileContext Context used for previous compilation of the current node
* @returns linkFn
*/
function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection,
- originalReplaceDirective, preLinkFns, postLinkFns) {
+ originalReplaceDirective, preLinkFns, postLinkFns, previousCompileContext) {
+ previousCompileContext = previousCompileContext || {};
+
var terminalPriority = -Number.MAX_VALUE,
- newScopeDirective = null,
- newIsolateScopeDirective = null,
- templateDirective = null,
+ newScopeDirective,
+ newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
+ templateDirective = previousCompileContext.templateDirective,
$compileNode = templateAttrs.$$element = jqLite(compileNode),
directive,
directiveName,
$template,
- transcludeDirective,
+ transcludeDirective = previousCompileContext.transcludeDirective,
replaceDirective = originalReplaceDirective,
childTranscludeFn = transcludeFn,
controllerDirectives,
@@ -815,19 +819,27 @@ function $CompileProvider($provide) {
}
if (directiveValue = directive.transclude) {
- assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
- transcludeDirective = directive;
+ // Special case ngRepeat so that we don't complain about duplicate transclusion, ngRepeat knows how to handle
+ // this on its own.
+ if (directiveName !== 'ngRepeat') {
+ assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
+ transcludeDirective = directive;
+ }
if (directiveValue == 'element') {
terminalPriority = directive.priority;
- $template = groupScan(compileNode, attrStart, attrEnd)
+ $template = groupScan(compileNode, attrStart, attrEnd);
$compileNode = templateAttrs.$$element =
jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' '));
compileNode = $compileNode[0];
replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode);
childTranscludeFn = compile($template, transcludeFn, terminalPriority,
- replaceDirective && replaceDirective.name);
+ replaceDirective && replaceDirective.name, {
+ newIsolateScopeDirective: newIsolateScopeDirective,
+ transcludeDirective: transcludeDirective,
+ templateDirective: templateDirective
+ });
} else {
$template = jqLite(JQLiteClone(compileNode)).contents();
$compileNode.html(''); // clear contents
@@ -889,7 +901,11 @@ function $CompileProvider($provide) {
}
nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
- templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns);
+ templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, {
+ newIsolateScopeDirective: newIsolateScopeDirective,
+ transcludeDirective: transcludeDirective,
+ templateDirective: templateDirective
+ });
ii = directives.length;
} else if (directive.compile) {
try {
@@ -1188,7 +1204,7 @@ function $CompileProvider($provide) {
function compileTemplateUrl(directives, $compileNode, tAttrs,
- $rootElement, childTranscludeFn, preLinkFns, postLinkFns) {
+ $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
var linkQueue = [],
afterTemplateNodeLinkFn,
afterTemplateChildLinkFn,
@@ -1231,7 +1247,7 @@ function $CompileProvider($provide) {
directives.unshift(derivedSyncDirective);
afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs,
- childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns);
+ childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, previousCompileContext);
forEach($rootElement, function(node, i) {
if (node == compileNode) {
$rootElement[i] = $compileNode[0];
diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js
index 1e6f6e26..9a69824a 100755
--- a/test/ng/compileSpec.js
+++ b/test/ng/compileSpec.js
@@ -1042,10 +1042,12 @@ describe('$compile', function() {
templateUrl: 'template.html'
}));
});
- inject(function($compile){
+ inject(function($compile, $httpBackend){
+ $httpBackend.whenGET('template.html').respond('<p>template.html</p>');
expect(function() {
$compile('<div><div class="sync async"></div></div>');
- }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [sync, async] asking for template on: '+
+ $httpBackend.flush();
+ }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [async, sync] asking for template on: '+
'<div class="sync async">');
});
});
@@ -1205,7 +1207,7 @@ describe('$compile', function() {
));
- it('should work when directive is a repeater', inject(
+ it('should work when directive is in a repeater', inject(
function($compile, $httpBackend, $rootScope) {
$httpBackend.expect('GET', 'hello.html').
respond('<span>i=<span ng-transclude></span>;</span>');
@@ -1317,7 +1319,7 @@ describe('$compile', function() {
});
- describe('template as function', function() {
+ describe('templateUrl as function', function() {
beforeEach(module(function() {
directive('myDirective', valueFn({
@@ -2745,23 +2747,81 @@ describe('$compile', function() {
});
- it('should only allow one transclude per element', function() {
+ it('should only allow one content transclusion per element', function() {
module(function() {
directive('first', valueFn({
- scope: {},
- restrict: 'CA',
- transclude: 'content'
+ transclude: true
}));
directive('second', valueFn({
- restrict: 'CA',
- transclude: 'content'
+ transclude: true
}));
});
inject(function($compile) {
expect(function() {
- $compile('<div class="first second"></div>');
+ $compile('<div first="" second=""></div>');
+ }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on: <div .+/);
+ });
+ });
+
+
+ it('should only allow one element transclusion per element', function() {
+ module(function() {
+ directive('first', valueFn({
+ transclude: 'element'
+ }));
+ directive('second', valueFn({
+ transclude: 'element'
+ }));
+ });
+ inject(function($compile) {
+ expect(function() {
+ $compile('<div first second></div>');
}).toThrowMinErr('$compile', 'multidir', 'Multiple directives [first, second] asking for transclusion on: ' +
- '<div class="first second ng-isolate-scope ng-scope">');
+ '<!-- first: -->');
+ });
+ });
+
+
+ it('should only allow one element transclusion per element when directives have different priorities', function() {
+ // we restart compilation in this case and we need to remember the duplicates during the second compile
+ // regression #3893
+ module(function() {
+ directive('first', valueFn({
+ transclude: 'element',
+ priority: 100
+ }));
+ directive('second', valueFn({
+ transclude: 'element'
+ }));
+ });
+ inject(function($compile) {
+ expect(function() {
+ $compile('<div first second></div>');
+ }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on: <div .+/);
+ });
+ });
+
+
+ it('should only allow one element transclusion per element when async replace directive is in the mix', function() {
+ module(function() {
+ directive('template', valueFn({
+ templateUrl: 'template.html',
+ replace: true
+ }));
+ directive('first', valueFn({
+ transclude: 'element',
+ priority: 100
+ }));
+ directive('second', valueFn({
+ transclude: 'element'
+ }));
+ });
+ inject(function($compile, $httpBackend) {
+ $httpBackend.expectGET('template.html').respond('<p second>template.html</p>');
+ $compile('<div template first></div>');
+ expect(function() {
+ $httpBackend.flush();
+ }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on: <p .+/);
});
});
diff --git a/test/ng/directive/ngRepeatSpec.js b/test/ng/directive/ngRepeatSpec.js
index 8dba13bf..9dde36e7 100644
--- a/test/ng/directive/ngRepeatSpec.js
+++ b/test/ng/directive/ngRepeatSpec.js
@@ -976,6 +976,43 @@ describe('ngRepeat', function() {
});
+ describe('compatibility', function() {
+
+ it('should allow mixing ngRepeat and another element transclusion directive', function() {
+ $compileProvider.directive('elmTrans', valueFn({
+ transclude: 'element',
+ controller: function($transclude, $scope, $element) {
+ $transclude(function(transcludedNodes) {
+ $element.after(']]').after(transcludedNodes).after('[[');
+ });
+ }
+ }));
+
+ inject(function($compile, $rootScope) {
+ element = $compile('<div><div ng-repeat="i in [1,2]" elm-trans>{{i}}</div></div>')($rootScope);
+ $rootScope.$digest();
+ expect(element.text()).toBe('[[1]][[2]]')
+ });
+ });
+
+
+ it('should allow mixing ngRepeat with ngInclude', inject(function($compile, $rootScope, $httpBackend) {
+ $httpBackend.whenGET('someTemplate.html').respond('<p>some template; </p>');
+ element = $compile('<div><div ng-repeat="i in [1,2]" ng-include="\'someTemplate.html\'"></div></div>')($rootScope);
+ $rootScope.$digest();
+ $httpBackend.flush();
+ expect(element.text()).toBe('some template; some template; ');
+ }));
+
+
+ it('should allow mixing ngRepeat with ngIf', inject(function($compile, $rootScope) {
+ element = $compile('<div><div ng-repeat="i in [1,2,3,4]" ng-if="i % 2 == 0">{{i}};</div></div>')($rootScope);
+ $rootScope.$digest();
+ expect(element.text()).toBe('2;4;');
+ }));
+ });
+
+
describe('ngRepeatStart', function () {
it('should grow multi-node repeater', inject(function($compile, $rootScope) {
$rootScope.show = false;