Skip to content

Commit 5be98e0

Browse files
jpekkalachristopherthielen
authored andcommitted
fix(uiView): do not leave initial view scope undestroyed (fix unhandled rejection)
This is a backport of #3164 from `master` to `legacy` Previously the contents of the initial view were linked and left undestroyed when the view was replaced with a subview. Because the view was not destroyed, directives like ngInclude assumed it was safe to compile and link asynchronous content to it. This would cause a ctreq error if the asynchronous content required another directive from the DOM. Now the initial view is either not linked at all or its scope is properly destroyed. Closes #3164
1 parent d7fae8e commit 5be98e0

File tree

2 files changed

+54
-1
lines changed

2 files changed

+54
-1
lines changed

src/viewDirective.js

+4
Original file line numberDiff line numberDiff line change
@@ -307,12 +307,16 @@ function $ViewDirectiveFill ( $compile, $controller, $state, $interpolate
307307
priority: -400,
308308
compile: function (tElement) {
309309
var initial = tElement.html();
310+
tElement.empty();
311+
310312
return function (scope, $element, attrs) {
311313
var current = $state.$current,
312314
name = getUiViewName(scope, attrs, $element, $interpolate),
313315
locals = current && current.locals[name];
314316

315317
if (! locals) {
318+
$element.html(initial);
319+
$compile($element.contents())(scope);
316320
return;
317321
}
318322

test/viewDirectiveSpec.js

+50-1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ describe('uiView', function () {
8888
controller: function() {
8989
this.someProperty = "value"
9090
},
91+
template: "{{vm.someProperty}}",
9192
controllerAs: "vm"
9293
},
9394
lState = {
@@ -324,7 +325,7 @@ describe('uiView', function () {
324325
});
325326

326327
it('should instantiate a controller with controllerAs', inject(function($state, $q) {
327-
elem.append($compile('<div><ui-view>{{vm.someProperty}}</ui-view></div>')(scope));
328+
elem.append($compile('<div><ui-view></ui-view></div>')(scope));
328329
$state.transitionTo(kState);
329330
$q.flush();
330331

@@ -734,3 +735,51 @@ describe("UiView", function() {
734735
}));
735736
}
736737
});
738+
739+
describe('uiView transclusion', function() {
740+
var scope, $compile, elem;
741+
742+
beforeEach(function() {
743+
app = angular.module('foo', []);
744+
745+
app.directive('scopeObserver', function() {
746+
return {
747+
restrict: 'E',
748+
link: function(scope) {
749+
scope.$emit('directiveCreated');
750+
scope.$on('$destroy', function() {
751+
scope.$emit('directiveDestroyed');
752+
});
753+
}
754+
};
755+
});
756+
});
757+
758+
beforeEach(module('ui.router', 'foo'));
759+
760+
beforeEach(module(function($stateProvider) {
761+
$stateProvider
762+
.state('a', { template: '<ui-view><scope-observer></scope-observer></ui-view>' })
763+
.state('a.b', { template: 'anything' });
764+
}));
765+
766+
beforeEach(inject(function ($rootScope, _$compile_) {
767+
scope = $rootScope.$new();
768+
$compile = _$compile_;
769+
elem = angular.element('<div>');
770+
}));
771+
772+
it('should not link the initial view and leave its scope undestroyed when a subview is activated', inject(function($state, $q) {
773+
var aliveCount = 0;
774+
scope.$on('directiveCreated', function() {
775+
aliveCount++;
776+
});
777+
scope.$on('directiveDestroyed', function() {
778+
aliveCount--;
779+
});
780+
elem.append($compile('<div><ui-view></ui-view></div>')(scope));
781+
$state.transitionTo('a.b');
782+
$q.flush();
783+
expect(aliveCount).toBe(0);
784+
}));
785+
});

0 commit comments

Comments
 (0)