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'; 'use strict';
ngRouteModule.directive('ngView', ngViewFactory); ngRouteModule.directive('ngView', ngViewFactory);
ngRouteModule.directive('ngView', ngViewFillContentFactory);
/** /**
* @ngdoc directive * @ngdoc directive
@ -166,8 +168,8 @@ ngRouteModule.directive('ngView', ngViewFactory);
* @description * @description
* Emitted every time the ngView content is reloaded. * Emitted every time the ngView content is reloaded.
*/ */
ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animate']; ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
function ngViewFactory( $route, $anchorScroll, $compile, $controller, $animate) { function ngViewFactory( $route, $anchorScroll, $animate) {
return { return {
restrict: 'ECA', restrict: 'ECA',
terminal: true, terminal: true,
@ -199,6 +201,7 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
if (template) { if (template) {
var newScope = scope.$new(); var newScope = scope.$new();
var current = $route.current;
// Note: This will also link all children of ng-view that were contained in the original // 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. // 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 // Note: We can't remove them in the cloneAttchFn of $transclude as that
// function is called before linking the content, which would apply child // function is called before linking the content, which would apply child
// directives to non existing elements. // directives to non existing elements.
var clone = $transclude(newScope, angular.noop); var clone = $transclude(newScope, function(clone) {
clone.html(template); $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { if (angular.isDefined(autoScrollExp)
if (angular.isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
&& (!autoScrollExp || scope.$eval(autoScrollExp))) { $anchorScroll();
$anchorScroll(); }
} });
cleanupLastView();
}); });
cleanupLastView();
var link = $compile(clone.contents()),
current = $route.current;
currentScope = current.scope = newScope;
currentElement = clone; currentElement = clone;
currentScope = current.scope = newScope;
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.$emit('$viewContentLoaded'); currentScope.$emit('$viewContentLoaded');
currentScope.$eval(onloadExp); currentScope.$eval(onloadExp);
} else { } 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() { describe('ngView animations', function() {