diff --git a/src/ng/compile.js b/src/ng/compile.js
index 84432647fe01..7754a8e6d69f 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -1191,9 +1191,13 @@ function $CompileProvider($provide) {
childTranscludeFn = compile($template, transcludeFn, terminalPriority,
replaceDirective && replaceDirective.name, {
- controllerDirectives: controllerDirectives,
- newIsolateScopeDirective: newIsolateScopeDirective,
- templateDirective: templateDirective,
+ // Don't pass in:
+ // - controllerDirectives - otherwise we'll create duplicates controllers
+ // - newIsolateScopeDirective or templateDirective - combining templates with
+ // element transclusion doesn't make sense.
+ //
+ // We need only transcludeDirective so that we prevent putting transclusion
+ // on the same element more than once.
transcludeDirective: transcludeDirective
});
} else {
diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js
index 72165b7ee67c..c6cba3a42fb1 100755
--- a/test/ng/compileSpec.js
+++ b/test/ng/compileSpec.js
@@ -2757,433 +2757,480 @@ describe('$compile', function() {
describe('transclude', function() {
- it('should compile get templateFn', function() {
- module(function() {
- directive('trans', function(log) {
- return {
- transclude: 'element',
- priority: 2,
- controller: function($transclude) { this.$transclude = $transclude; },
- compile: function(element, attrs, template) {
- log('compile: ' + angular.mock.dump(element));
- return function(scope, element, attrs, ctrl) {
- log('link');
- var cursor = element;
- template(scope.$new(), function(clone) {cursor.after(cursor = clone)});
- ctrl.$transclude(function(clone) {cursor.after(clone)});
- };
+
+ describe('content transclusion', function() {
+
+ it('should support transclude directive', function() {
+ module(function() {
+ directive('trans', function() {
+ return {
+ transclude: 'content',
+ replace: true,
+ scope: true,
+ template: '
- W:{{$parent.$id}}-{{$id}};
'
}
- }
+ });
+ });
+ inject(function(log, $rootScope, $compile) {
+ element = $compile('T:{{$parent.$id}}-{{$id}};
')
+ ($rootScope);
+ $rootScope.$apply();
+ expect(element.text()).toEqual('W:001-002;T:001-003;');
+ expect(jqLite(element.find('span')[0]).text()).toEqual('T:001-003');
+ expect(jqLite(element.find('span')[1]).text()).toEqual(';');
});
});
- inject(function(log, $rootScope, $compile) {
- element = $compile('')
- ($rootScope);
- $rootScope.$apply();
- expect(log).toEqual('compile: ; link; LOG; LOG; HIGH');
- expect(element.text()).toEqual('001-002;001-003;');
- });
- });
- it('should support transclude directive', function() {
- module(function() {
- directive('trans', function() {
- return {
+ it('should transclude transcluded content', function() {
+ module(function() {
+ directive('book', valueFn({
transclude: 'content',
- replace: true,
- scope: true,
- template: '- W:{{$parent.$id}}-{{$id}};
'
+ template: ''
+ }));
+ directive('chapter', valueFn({
+ transclude: 'content',
+ templateUrl: 'chapter.html'
+ }));
+ directive('section', valueFn({
+ transclude: 'content',
+ template: ''
+ }));
+ return function($httpBackend) {
+ $httpBackend.
+ expect('GET', 'chapter.html').
+ respond('');
}
});
- });
- inject(function(log, $rootScope, $compile) {
- element = $compile('T:{{$parent.$id}}-{{$id}};
')
- ($rootScope);
- $rootScope.$apply();
- expect(element.text()).toEqual('W:001-002;T:001-003;');
- expect(jqLite(element.find('span')[0]).text()).toEqual('T:001-003');
- expect(jqLite(element.find('span')[1]).text()).toEqual(';');
- });
- });
+ inject(function(log, $rootScope, $compile, $httpBackend) {
+ element = $compile('')($rootScope);
+ $rootScope.$apply();
+ expect(element.text()).toEqual('book-');
- it('should transclude transcluded content', function() {
- module(function() {
- directive('book', valueFn({
- transclude: 'content',
- template: ''
- }));
- directive('chapter', valueFn({
- transclude: 'content',
- templateUrl: 'chapter.html'
- }));
- directive('section', valueFn({
- transclude: 'content',
- template: ''
- }));
- return function($httpBackend) {
- $httpBackend.
- expect('GET', 'chapter.html').
- respond('');
- }
+ $httpBackend.flush();
+ $rootScope.$apply();
+ expect(element.text()).toEqual('book-chapter-section-![(paragraph)]!');
+ });
});
- inject(function(log, $rootScope, $compile, $httpBackend) {
- element = $compile('')($rootScope);
- $rootScope.$apply();
- expect(element.text()).toEqual('book-');
- $httpBackend.flush();
- $rootScope.$apply();
- expect(element.text()).toEqual('book-chapter-section-![(paragraph)]!');
+ it('should only allow one content transclusion per element', function() {
+ module(function() {
+ directive('first', valueFn({
+ transclude: true
+ }));
+ directive('second', valueFn({
+ transclude: true
+ }));
+ });
+ inject(function($compile) {
+ expect(function() {
+ $compile('');
+ }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on: ');
- }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on: ',
+ link: function(scope, element) {
+ scope.$watch(
+ 'show',
+ function(show) {
+ if (!show) {
+ element.find('div').find('div').remove();
+ }
+ }
+ );
+ }
+ }));
+ });
+ inject(function($compile, $rootScope) {
+ $rootScope.username = 'Misko';
+ $rootScope.select = true;
+ element = $compile(
+ '')
+ ($rootScope);
+ $rootScope.$apply();
+ expect(element.text()).toEqual('Hello: Misko!user: Misko');
+ var widgetScope = $rootScope.$$childHead;
+ var transcludeScope = widgetScope.$$nextSibling;
+ expect(widgetScope.name).toEqual('Misko');
+ expect(widgetScope.$parent).toEqual($rootScope);
+ expect(transcludeScope.$parent).toEqual($rootScope);
- 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('');
- }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [first, second] asking for transclusion on: ' +
- '');
+ $rootScope.select = false;
+ $rootScope.$apply();
+ expect(element.text()).toEqual('Hello: Misko!');
+ expect(widgetScope.$$nextSibling).toEqual(null);
+ });
});
- });
- 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('');
- }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on: '
+ };
+ });
+ });
+ inject(function(log, $rootScope, $compile) {
+ element = $compile('')
+ ($rootScope);
+ $rootScope.$apply();
+ expect(jqLite(element.find('span')[0]).text()).toEqual('I:');
+ expect(jqLite(element.find('span')[1]).text()).toEqual('T:true');
+ });
});
- });
- 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('template.html
');
- $compile('');
- expect(function() {
- $httpBackend.flush();
- }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on: old stuff! '
+ };
+ });
+ });
+ inject(function(log, $rootScope, $compile) {
+ element = $compile('
unicorn!
')($rootScope);
+ $rootScope.$apply();
+ expect(sortedHtml(element.html())).toEqual('unicorn!
');
+ });
});
- });
- it('should remove transclusion scope, when the DOM is destroyed', function() {
- module(function() {
- directive('box', valueFn({
- transclude: 'content',
- scope: { name: '=', show: '=' },
- template: '',
- link: function(scope, element) {
- scope.$watch(
- 'show',
- function(show) {
- if (!show) {
- element.find('div').find('div').remove();
- }
- }
- );
+ it('should throw on an ng-translude element inside no transclusion directive', function() {
+ inject(function ($rootScope, $compile) {
+ // we need to do this because different browsers print empty attributres differently
+ try {
+ $compile('')($rootScope);
+ } catch(e) {
+ expect(e.message).toMatch(new RegExp(
+ '^\\\[ngTransclude:orphan\\\] ' +
+ 'Illegal use of ngTransclude directive in the template! ' +
+ 'No parent directive that requires a transclusion found\. ' +
+ 'Element: ')
- ($rootScope);
- $rootScope.$apply();
- expect(element.text()).toEqual('Hello: Misko!user: Misko');
-
- var widgetScope = $rootScope.$$childHead;
- var transcludeScope = widgetScope.$$nextSibling;
- expect(widgetScope.name).toEqual('Misko');
- expect(widgetScope.$parent).toEqual($rootScope);
- expect(transcludeScope.$parent).toEqual($rootScope);
-
- $rootScope.select = false;
- $rootScope.$apply();
- expect(element.text()).toEqual('Hello: Misko!');
- expect(widgetScope.$$nextSibling).toEqual(null);
+ });
});
- });
- it('should support transcluded element on root content', function() {
- var comment;
- module(function() {
- directive('transclude', valueFn({
- transclude: 'element',
- compile: function(element, attr, linker) {
- return function(scope, element, attr) {
- comment = element;
+ it('should make the result of a transclusion available to the parent directive in post-linking phase' +
+ '(template)', function() {
+ module(function() {
+ directive('trans', function(log) {
+ return {
+ transclude: true,
+ template: '',
+ link: {
+ pre: function($scope, $element) {
+ log('pre(' + $element.text() + ')');
+ },
+ post: function($scope, $element) {
+ log('post(' + $element.text() + ')');
+ }
+ }
};
- }
- }));
- });
- inject(function($compile, $rootScope) {
- var element = jqLite('').contents();
- expect(element.length).toEqual(3);
- expect(nodeName_(element[1])).toBe('DIV');
- $compile(element)($rootScope);
- expect(nodeName_(element[1])).toBe('#comment');
- expect(nodeName_(comment)).toBe('#comment');
+ });
+ });
+ inject(function(log, $rootScope, $compile) {
+ element = $compile('unicorn!
')($rootScope);
+ $rootScope.$apply();
+ expect(log).toEqual('pre(); post(unicorn!)');
+ });
});
- });
- it('should safely create transclude comment node and not break with "-->"',
- inject(function($rootScope) {
- // see: https://github.com/angular/angular.js/issues/1740
- element = $compile('')($rootScope);
- $rootScope.$digest();
+ it('should make the result of a transclusion available to the parent directive in post-linking phase' +
+ '(templateUrl)', function() {
+ // when compiling an async directive the transclusion is always processed before the directive
+ // this is different compared to sync directive. delaying the transclusion makes little sense.
- expect(element.text()).toBe('-->|x|');
- }));
-
-
- it('should add a $$transcluded property onto the transcluded scope', function() {
- module(function() {
- directive('trans', function() {
- return {
- transclude: true,
- replace: true,
- scope: true,
- template: ''
- };
+ module(function() {
+ directive('trans', function(log) {
+ return {
+ transclude: true,
+ templateUrl: 'trans.html',
+ link: {
+ pre: function($scope, $element) {
+ log('pre(' + $element.text() + ')');
+ },
+ post: function($scope, $element) {
+ log('post(' + $element.text() + ')');
+ }
+ }
+ };
+ });
});
- });
- inject(function(log, $rootScope, $compile) {
- element = $compile('')
- ($rootScope);
- $rootScope.$apply();
- expect(jqLite(element.find('span')[0]).text()).toEqual('I:');
- expect(jqLite(element.find('span')[1]).text()).toEqual('T:true');
- });
- });
-
+ inject(function(log, $rootScope, $compile, $templateCache) {
+ $templateCache.put('trans.html', '');
- it('should clear contents of the ng-translude element before appending transcluded content',
- function() {
- module(function() {
- directive('trans', function() {
- return {
- transclude: true,
- template: 'old stuff!
'
- };
+ element = $compile('unicorn!
')($rootScope);
+ $rootScope.$apply();
+ expect(log).toEqual('pre(); post(unicorn!)');
});
});
- inject(function(log, $rootScope, $compile) {
- element = $compile('unicorn!
')($rootScope);
- $rootScope.$apply();
- expect(sortedHtml(element.html())).toEqual('unicorn!
');
- });
- });
- it('should throw on an ng-translude element inside no transclusion directive', function() {
- inject(function ($rootScope, $compile) {
- // we need to do this because different browsers print empty attributres differently
- try {
- $compile('')($rootScope);
- } catch(e) {
- expect(e.message).toMatch(new RegExp(
- '^\\\[ngTransclude:orphan\\\] ' +
- 'Illegal use of ngTransclude directive in the template! ' +
- 'No parent directive that requires a transclusion found\. ' +
- 'Element: ',
+ link: {
+ pre: function($scope, $element) {
+ log('pre(' + $element.text() + ')');
+ },
+ post: function($scope, $element) {
+ log('post(' + $element.text() + ')');
+ }
+ }
+ };
+ });
+ });
+ inject(function(log, $rootScope, $compile) {
+ element = $compile('unicorn!
')($rootScope);
+ $rootScope.$apply();
+ expect(log).toEqual('pre(); post(unicorn!)');
+ });
});
- });
- it('should make the result of a transclusion available to the parent directive in post-linking phase (template)',
- function() {
- module(function() {
- directive('trans', function(log) {
- return {
- transclude: true,
- template: '',
- link: {
- pre: function($scope, $element) {
- log('pre(' + $element.text() + ')');
- },
- post: function($scope, $element) {
- log('post(' + $element.text() + ')');
+ it('should make the result of a transclusion available to the parent *replace* directive in post-linking phase' +
+ ' (templateUrl)', function() {
+ module(function() {
+ directive('replacedTrans', function(log) {
+ return {
+ transclude: true,
+ replace: true,
+ templateUrl: 'trans.html',
+ link: {
+ pre: function($scope, $element) {
+ log('pre(' + $element.text() + ')');
+ },
+ post: function($scope, $element) {
+ log('post(' + $element.text() + ')');
+ }
}
- }
- };
+ };
+ });
+ });
+ inject(function(log, $rootScope, $compile, $templateCache) {
+ $templateCache.put('trans.html', '');
+
+ element = $compile('unicorn!
')($rootScope);
+ $rootScope.$apply();
+ expect(log).toEqual('pre(); post(unicorn!)');
});
- });
- inject(function(log, $rootScope, $compile) {
- element = $compile('unicorn!
')($rootScope);
- $rootScope.$apply();
- expect(log).toEqual('pre(); post(unicorn!)');
});
});
- it('should make the result of a transclusion available to the parent directive in post-linking phase (templateUrl)',
- function() {
- // when compiling an async directive the transclusion is always processed before the directive
- // this is different compared to sync directive. delaying the transclusion makes little sense.
+ describe('element transclusion', function() {
- module(function() {
- directive('trans', function(log) {
- return {
- transclude: true,
- templateUrl: 'trans.html',
- link: {
- pre: function($scope, $element) {
- log('pre(' + $element.text() + ')');
- },
- post: function($scope, $element) {
- log('post(' + $element.text() + ')');
+ it('should support basic element transclusion', function() {
+ module(function() {
+ directive('trans', function(log) {
+ return {
+ transclude: 'element',
+ priority: 2,
+ controller: function($transclude) { this.$transclude = $transclude; },
+ compile: function(element, attrs, template) {
+ log('compile: ' + angular.mock.dump(element));
+ return function(scope, element, attrs, ctrl) {
+ log('link');
+ var cursor = element;
+ template(scope.$new(), function(clone) {cursor.after(cursor = clone)});
+ ctrl.$transclude(function(clone) {cursor.after(clone)});
+ };
}
}
- };
+ });
+ });
+ inject(function(log, $rootScope, $compile) {
+ element = $compile('')
+ ($rootScope);
+ $rootScope.$apply();
+ expect(log).toEqual('compile: ; link; LOG; LOG; HIGH');
+ expect(element.text()).toEqual('001-002;001-003;');
});
});
- inject(function(log, $rootScope, $compile, $templateCache) {
- $templateCache.put('trans.html', '');
- element = $compile('unicorn!
')($rootScope);
- $rootScope.$apply();
- expect(log).toEqual('pre(); post(unicorn!)');
+
+ 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('');
+ }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [first, second] asking for transclusion on: ' +
+ '');
+ });
});
- });
- it('should make the result of a transclusion available to the parent *replace* directive in post-linking phase (template)',
- function() {
- module(function() {
- directive('replacedTrans', function(log) {
- return {
- transclude: true,
- replace: true,
- template: '',
- link: {
- pre: function($scope, $element) {
- log('pre(' + $element.text() + ')');
- },
- post: function($scope, $element) {
- log('post(' + $element.text() + ')');
- }
- }
- };
+ 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('');
+ }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on: unicorn!
')($rootScope);
- $rootScope.$apply();
- expect(log).toEqual('pre(); post(unicorn!)');
+
+
+ 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('template.html
');
+ $compile('');
+ expect(function() {
+ $httpBackend.flush();
+ }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on: before
after').contents();
+ expect(element.length).toEqual(3);
+ expect(nodeName_(element[1])).toBe('DIV');
+ $compile(element)($rootScope);
+ expect(nodeName_(element[1])).toBe('#comment');
+ expect(nodeName_(comment)).toBe('#comment');
});
});
- inject(function(log, $rootScope, $compile, $templateCache) {
- $templateCache.put('trans.html', '');
- element = $compile('unicorn!
')($rootScope);
- $rootScope.$apply();
- expect(log).toEqual('pre(); post(unicorn!)');
+
+ it('should terminate compilation only for element trasclusion', function() {
+ module(function() {
+ directive('elementTrans', function(log) {
+ return {
+ transclude: 'element',
+ priority: 50,
+ compile: log.fn('compile:elementTrans')
+ };
+ });
+ directive('regularTrans', function(log) {
+ return {
+ transclude: true,
+ priority: 50,
+ compile: log.fn('compile:regularTrans')
+ };
+ });
+ });
+ inject(function(log, $compile, $rootScope) {
+ $compile('')($rootScope);
+ expect(log).toEqual('compile:elementTrans; compile:regularTrans; regular');
+ });
});
- });
- it('should terminate compilation only for element trasclusion', function() {
- module(function() {
- directive('elementTrans', function(log) {
- return {
- transclude: 'element',
- priority: 50,
- compile: log.fn('compile:elementTrans')
- };
+ it('should instantiate high priority controllers only once, but low priority ones each time we transclude',
+ function() {
+ module(function() {
+ directive('elementTrans', function(log) {
+ return {
+ transclude: 'element',
+ priority: 50,
+ controller: function($transclude, $element) {
+ log('controller:elementTrans');
+ $transclude(function(clone) {
+ $element.after(clone);
+ });
+ $transclude(function(clone) {
+ $element.after(clone);
+ });
+ $transclude(function(clone) {
+ $element.after(clone);
+ });
+ }
+ };
+ });
+ directive('normalDir', function(log) {
+ return {
+ controller: function() {
+ log('controller:normalDir');
+ }
+ };
+ });
});
- directive('regularTrans', function(log) {
- return {
- transclude: true,
- priority: 50,
- compile: log.fn('compile:regularTrans')
- };
+ inject(function($compile, $rootScope, log) {
+ element = $compile('')($rootScope);
+ expect(log).toEqual([
+ 'controller:elementTrans',
+ 'controller:normalDir',
+ 'controller:normalDir',
+ 'controller:normalDir'
+ ]);
});
});
- inject(function(log, $compile, $rootScope) {
- $compile('')($rootScope);
- expect(log).toEqual('compile:elementTrans; compile:regularTrans; regular');
- });
});
+
+
+ it('should safely create transclude comment node and not break with "-->"',
+ inject(function($rootScope) {
+ // see: https://github.com/angular/angular.js/issues/1740
+ element = $compile('')($rootScope);
+ $rootScope.$digest();
+
+ expect(element.text()).toBe('-->|x|');
+ }));
});