fix(ngView): Add template to DOM before linking other directives

The template needs to be added to the DOM before
other directives at the same element as `ngView` are linked.

Related to #5247.
This commit is contained in:
Tobias Bosch 2013-12-10 16:45:54 -08:00
parent 43072e3812
commit f8944efe70
2 changed files with 87 additions and 27 deletions

View file

@ -1,6 +1,8 @@
'use strict';
ngRouteModule.directive('ngView', ngViewFactory);
ngRouteModule.directive('ngView', ngViewFillContentFactory);
/**
* @ngdoc directive
@ -166,8 +168,8 @@ ngRouteModule.directive('ngView', ngViewFactory);
* @description
* Emitted every time the ngView content is reloaded.
*/
ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animate'];
function ngViewFactory( $route, $anchorScroll, $compile, $controller, $animate) {
ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
function ngViewFactory( $route, $anchorScroll, $animate) {
return {
restrict: 'ECA',
terminal: true,
@ -199,6 +201,7 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
if (template) {
var newScope = scope.$new();
var current = $route.current;
// Note: This will also link all children of ng-view that were contained in the original
// html. If that content contains controllers, ... they could pollute/change the scope.
@ -206,34 +209,18 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
// Note: We can't remove them in the cloneAttchFn of $transclude as that
// function is called before linking the content, which would apply child
// directives to non existing elements.
var clone = $transclude(newScope, angular.noop);
clone.html(template);
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
if (angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
var clone = $transclude(newScope, function(clone) {
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
if (angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
});
cleanupLastView();
});
cleanupLastView();
var link = $compile(clone.contents()),
current = $route.current;
currentScope = current.scope = newScope;
currentElement = clone;
if (current.controller) {
locals.$scope = currentScope;
var controller = $controller(current.controller, locals);
if (current.controllerAs) {
currentScope[current.controllerAs] = controller;
}
clone.data('$ngControllerController', controller);
clone.children().data('$ngControllerController', controller);
}
link(currentScope);
currentScope = current.scope = newScope;
currentScope.$emit('$viewContentLoaded');
currentScope.$eval(onloadExp);
} else {
@ -243,3 +230,36 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
}
};
}
// This directive is called during the $transclude call of the first `ngView` directive.
// It will replace and compile the content of the element with the loaded template.
// We need this directive so that the element content is already filled when
// the link function of another directive on the same element as ngView
// is called.
ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route'];
function ngViewFillContentFactory($compile, $controller, $route) {
return {
restrict: 'ECA',
priority: -400,
link: function(scope, $element) {
var current = $route.current,
locals = current.locals;
$element.html(locals.$template);
var link = $compile($element.contents());
if (current.controller) {
locals.$scope = scope;
var controller = $controller(current.controller, locals);
if (current.controllerAs) {
scope[current.controllerAs] = controller;
}
$element.data('$ngControllerController', controller);
$element.children().data('$ngControllerController', controller);
}
link(scope);
}
};
}

View file

@ -582,6 +582,46 @@ describe('ngView and transcludes', function() {
});
});
it('should link directives on the same element after the content has been loaded', function() {
var contentOnLink;
module(function($compileProvider, $routeProvider) {
$routeProvider.when('/view', {template: 'someContent'});
$compileProvider.directive('test', function() {
return {
link: function(scope, element) {
contentOnLink = element.text();
}
};
});
});
inject(function($compile, $rootScope, $location) {
element = $compile('<div><div ng-view test></div>')($rootScope);
$location.url('/view');
$rootScope.$apply();
expect(contentOnLink).toBe('someContent');
});
});
it('should add the content to the element before compiling it', function() {
var root;
module(function($compileProvider, $routeProvider) {
$routeProvider.when('/view', {template: '<span test></span>'});
$compileProvider.directive('test', function() {
return {
link: function(scope, element) {
root = element.parent().parent();
}
};
});
});
inject(function($compile, $rootScope, $location) {
element = $compile('<div><div ng-view></div>')($rootScope);
$location.url('/view');
$rootScope.$apply();
expect(root[0]).toBe(element[0]);
});
});
});
describe('ngView animations', function() {