Skip to content

Commit 86e5655

Browse files
committed
feat($compile): implement an $onDestroy lifecycle hook for controllers
As a complement to the `$onInit` lifecycle hook, the `$onDestroy` lifecycle hook allows one to wire up cleanup behavior in a controller that will be invoked for you at the appropriate time. Fixes angular#14020
1 parent 0749eb4 commit 86e5655

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

src/ng/compile.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,6 +2426,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
24262426
}
24272427
});
24282428

2429+
(isolateScope || scope).$on('$destroy', function() {
2430+
forEach(elementControllers, function(controller) {
2431+
if (isFunction(controller.instance.$onDestroy)) {
2432+
controller.instance.$onDestroy();
2433+
}
2434+
});
2435+
});
2436+
24292437
// PRELINKING
24302438
for (i = 0, ii = preLinkFns.length; i < ii; i++) {
24312439
linkFn = preLinkFns[i];

test/ng/compileSpec.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5351,6 +5351,60 @@ describe('$compile', function() {
53515351
});
53525352
});
53535353

5354+
it("should call `controller.$onDestroy`, if provided after the scope has been destroyed", function() {
5355+
function check() {
5356+
/*jshint validthis:true */
5357+
expect(this.element.controller('d1').id).toEqual(1);
5358+
expect(this.element.controller('d2').id).toEqual(2);
5359+
}
5360+
5361+
function Controller1($element) { this.id = 1; this.element = $element; }
5362+
Controller1.prototype.$onDestroy = jasmine.createSpy('$onDestroy').andCallFake(check);
5363+
5364+
function Controller2($element) { this.id = 2; this.element = $element; }
5365+
Controller2.prototype.$onDestroy = jasmine.createSpy('$onDestroy').andCallFake(check);
5366+
5367+
angular.module('my', [])
5368+
.directive('d1', valueFn({ controller: Controller1 }))
5369+
.directive('d2', valueFn({ controller: Controller2 }));
5370+
5371+
module('my');
5372+
inject(function($compile, $rootScope) {
5373+
var scope = $rootScope.$new();
5374+
element = $compile('<div d1 d2></div>')(scope);
5375+
scope.$destroy();
5376+
expect(Controller1.prototype.$onDestroy).toHaveBeenCalledOnce();
5377+
expect(Controller2.prototype.$onDestroy).toHaveBeenCalledOnce();
5378+
});
5379+
});
5380+
5381+
it("should call `controller.$onDestroy`, if provided after the isolateScope has been destroyed", function() {
5382+
function check() {
5383+
/*jshint validthis:true */
5384+
expect(this.element.controller('d1').id).toEqual(1);
5385+
expect(this.element.controller('d2').id).toEqual(2);
5386+
}
5387+
5388+
function Controller1($element) { this.id = 1; this.element = $element; }
5389+
Controller1.prototype.$onDestroy = jasmine.createSpy('$onDestroy').andCallFake(check);
5390+
5391+
function Controller2($element) { this.id = 2; this.element = $element; }
5392+
Controller2.prototype.$onDestroy = jasmine.createSpy('$onDestroy').andCallFake(check);
5393+
5394+
angular.module('my', [])
5395+
.directive('d1', valueFn({ controller: Controller1 }))
5396+
.directive('d2', valueFn({ controller: Controller2 }));
5397+
5398+
module('my');
5399+
inject(function($compile, $rootScope) {
5400+
var scope = $rootScope.$new(true);
5401+
element = $compile('<div d1 d2></div>')(scope);
5402+
scope.$destroy();
5403+
expect(Controller1.prototype.$onDestroy).toHaveBeenCalledOnce();
5404+
expect(Controller2.prototype.$onDestroy).toHaveBeenCalledOnce();
5405+
});
5406+
});
5407+
53545408
describe('should not overwrite @-bound property each digest when not present', function() {
53555409
it('when creating new scope', function() {
53565410
module(function($compileProvider) {

0 commit comments

Comments
 (0)