Skip to content

Commit 37d6f9a

Browse files
jpekkalachristopherthielen
authored andcommitted
fix(uiView): do not leave initial view scope undestroyed (#3164)
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 #1896
1 parent 9795c8f commit 37d6f9a

File tree

2 files changed

+56
-3
lines changed

2 files changed

+56
-3
lines changed

src/ng1/directives/viewDirective.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -326,10 +326,15 @@ function $ViewDirectiveFill ($compile: ICompileService, $controller: IController
326326
priority: -400,
327327
compile: function (tElement: JQuery) {
328328
let initial = tElement.html();
329+
tElement.empty();
329330

330331
return function (scope: IScope, $element: JQuery) {
331332
let data: UIViewData = $element.data('$uiView');
332-
if (!data) return;
333+
if (!data) {
334+
$element.html(initial);
335+
$compile($element.contents())(scope);
336+
return;
337+
}
333338

334339
let cfg: Ng1ViewConfig = data.$cfg || <any> { viewDecl: {} };
335340
$element.html(cfg.template || initial);

test/viewDirectiveSpec.js

+50-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ describe('uiView', function () {
8888
controller: function() {
8989
this.someProperty = "value"
9090
},
91-
template: "hi",
91+
template: "{{vm.someProperty}}",
9292
controllerAs: "vm"
9393
},
9494
lState = {
@@ -324,7 +324,7 @@ describe('uiView', function () {
324324
});
325325

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

@@ -726,6 +726,54 @@ describe("UiView", function() {
726726
}));
727727
});
728728

729+
describe('uiView transclusion', function() {
730+
var scope, $compile, elem;
731+
732+
beforeEach(function() {
733+
app = angular.module('foo', []);
734+
735+
app.directive('scopeObserver', function() {
736+
return {
737+
restrict: 'E',
738+
link: function(scope) {
739+
scope.$emit('directiveCreated');
740+
scope.$on('$destroy', function() {
741+
scope.$emit('directiveDestroyed');
742+
});
743+
}
744+
};
745+
});
746+
});
747+
748+
beforeEach(module('ui.router', 'foo'));
749+
750+
beforeEach(module(function($stateProvider) {
751+
$stateProvider
752+
.state('a', { template: '<ui-view><scope-observer></scope-observer></ui-view>' })
753+
.state('a.b', { template: 'anything' });
754+
}));
755+
756+
beforeEach(inject(function ($rootScope, _$compile_) {
757+
scope = $rootScope.$new();
758+
$compile = _$compile_;
759+
elem = angular.element('<div>');
760+
}));
761+
762+
it('should not link the initial view and leave its scope undestroyed when a subview is activated', inject(function($state, $q) {
763+
var aliveCount = 0;
764+
scope.$on('directiveCreated', function() {
765+
aliveCount++;
766+
});
767+
scope.$on('directiveDestroyed', function() {
768+
aliveCount--;
769+
});
770+
elem.append($compile('<div><ui-view></ui-view></div>')(scope));
771+
$state.transitionTo('a.b');
772+
$q.flush();
773+
expect(aliveCount).toBe(0);
774+
}));
775+
});
776+
729777
describe('uiView controllers or onEnter handlers', function() {
730778
var el, template, scope, document, count;
731779

0 commit comments

Comments
 (0)