8000 feat($compile): add $onDestroy directive lifecycle hook by bobbijvoet · Pull Request #14127 · angular/angular.js · GitHub
[go: up one dir, main page]

Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

feat($compile): add $onDestroy directive lifecycle hook #14127

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@
* * `$onInit` - Called on each controller after all the controllers on an element have been constructed and
* had their bindings initialized (and before the pre & post linking functions for the directives on
* this element). This is a good place to put initialization code for your controller.
* * `$onDestroy` - Called on each controller when the directive has been destroyed. This is a good place to put
* code for tearing down your controller like for example removing event listeners.
*
* #### `require`
* Require another directive and inject its controller as the fourth argument to the linking function. The
Expand Down Expand Up @@ -2419,11 +2421,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
});

// Trigger the `$onInit` method on all controllers that have one
// Trigger the `$onInit` method on all controllers that have one,
// and trigger `$onDestroy` method if present and when the element emits `$destroy` event
forEach(elementControllers, function(controller) {
if (isFunction(controller.instance.$onInit)) {
controller.instance.$onInit();
}

$element.on('$destroy', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The $destroy event won't function properly when you have a transclude: "element" directive since events are not fired on the comment node that becomes $element.

http://jsfiddle.net/rogqp47y/17/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it should be implemented using $scope.$on('$destroy', ... ?

if (isFunction(controller.instance.$onDestroy)) {
controller.instance.$onDestroy();
}
});
});

// PRELINKING
Expand Down
98 changes: 97 additions & 1 deletion test/ng/compileSpec.js
10000
Original file line number Diff line number Diff line change
Expand Up @@ -4584,6 +4584,7 @@ describe('$compile', function() {
"class Foo {\n" +
" constructor($scope) {}\n" +
" $onInit() { this.check(); }\n" +
" $onDestroy() {}\n" +
" check() {\n" +
" expect(this.data).toEqualData({\n" +
" 'foo': 'bar',\n" +
Expand All @@ -4599,6 +4600,7 @@ describe('$compile', function() {
" }\n" +
"}");
spyOn(Controller.prototype, '$onInit').andCallThrough();
spyOn(Controller.prototype, '$onDestroy').andCallThrough();

module(function($compileProvider) {
$compileProvider.directive('fooDir', valueFn({
Expand All @@ -4625,7 +4627,11 @@ describe('$compile', function() {
'dir-str="Hello, {{whom}}!" ' +
'dir-fn="fn()"></div>')($rootScope);
expect(Controller.prototype.$onInit).toHaveBeenCalled();
expect(Controller.prototype.$onDestroy).not.toHaveBeenCalled();
expect(controllerCalled).toBe(true);
element.remove();
expect(Controller.prototype.$onDestroy).toHaveBeenCalled();

});
/*jshint +W061 */
});
Expand Down Expand Up @@ -5351,6 +5357,78 @@ describe('$compile', function() {
});
});

it('should call `controller.$onDestroy`, if provided when the element is removed', function() {

function check() {
/*jshint validthis:true */
expect(this.element.controller('d1').id).toEqual(1);
expect(this.element.controller('d2').id).toEqual(2);
}
function Controller1($element) { this.id = 1; this.element = $element; }
Controller1.prototype.$onDestroy = jasmine.createSpy('$onDestroy').andCallFake(check);

function Controller2($element) { this.id = 2; this.element = $element; }
Controller2.prototype.$onDestroy = jasmine.createSpy('$onDestroy').andCallFake(check);

angular.module('my', [])
.directive('d1', valueFn({ controller: Controller1 }))
.directive('d2', valueFn({ controller: Controller2 }));

module('my');
inject(function($compile, $rootScope) {
element = $compile('<div d1 d2></div>')($rootScope);
element.remove();
expect(Controller1.prototype.$onDestroy).toHaveBeenCalledOnce();
expect(Controller2.prototype.$onDestroy).toHaveBeenCalledOnce();
});
});

it('should call `controller.$onDestroy`, if provided when the directive is removed using ngIf', function() {

function check() {
/*jshint validthis:true */
expect(this.element.controller('d1').id).toEqual(1);
}

function Controller1($element) { this.id = 1; this.element = $element; }
Controller1.prototype.$onDestroy = jasmine.createSpy('$onDestroy').andCallFake(check);

angular.module('my', [])
.directive('d1', valueFn({ controller: Controller1 }));

module('my');
inject(function($compile, $rootScope) {
element = $compile('<div><div ng-if="t"><d1></d1></div></div>')($rootScope);
$rootScope.t = true;
$rootScope.$apply();

$rootScope.t = false;
$rootScope.$apply();

expect(Controller1.prototype.$onDestroy).toHaveBeenCalledOnce();
});
});

it('should call `controller.$onDestroy`, when provided after controller initialization', function() {

function Controller1() {
this.setDestroy = function() {
Controller1.prototype.$onDestroy = jasmine.createSpy('$onDestroy');
};
}

angular.module('my', [])
.directive('d1', valueFn({ controller: Controller1 }));

module('my');
inject(function($compile, $rootScope) {
element = $compile('<div d1></div>')($rootScope);
element.controller('d1').setDestroy();
element.remove();
expect(Controller1.prototype.$onDestroy).toHaveBeenCalledOnce();
});
});

describe('should not overwrite @-bound property each digest when not present', function() {
it('when creating new scope', function() {
module(function($compileProvider) {
Expand Down Expand Up @@ -5741,6 +5819,8 @@ describe('$compile', function() {
siblingController = this.friend;
};
spyOn(MeController.prototype, '$onInit').andCallThrough();
MeController.prototype.$onDestroy = function() {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Place this line right under MeController.prototype.$onInit = ....

spyOn(MeController.prototype, '$onDestroy').andCallThrough();

angular.module('my', [])
.directive('me', function() {
Expand Down Expand Up @@ -5770,8 +5850,12 @@ describe('$compile', function() {
inject(function($compile, $rootScope, meDirective) {
element = $compile('<parent><me sibling></me></parent>')($rootScope);
expect(MeController.prototype.$onInit).toHaveBeenCalled();
expect(MeController.prototype.$onDestroy).not.toHaveBeenCalled();
expect(parentController).toEqual(jasmine.any(ParentController));
expect(siblingController).toEqual(jasmine.any(SiblingController));
element.remove();
expect(MeController.prototype.$onDestroy).toHaveBeenCalled();

});
});

Expand All @@ -5787,6 +5871,8 @@ describe('$compile', function() {
siblingController = this.friend;
};
spyOn(MeController.prototype, '$onInit').andCallThrough();
MeController.prototype.$onDestroy = function() {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Place this line right under MeController.prototype.$onInit = ....

spyOn(MeController.prototype, '$onDestroy').andCallThrough();

angular.module('my', [])
.directive('me', function() {
Expand Down Expand Up @@ -5814,8 +5900,12 @@ describe('$compile', function() {
inject(function($compile, $rootScope, meDirective) {
element = $compile('<parent><me sibling></me></parent>')($rootScope);
expect(MeController.prototype.$onInit).toHaveBeenCalled();
expect(MeController.prototype.$onDestroy).not.toHaveBeenCalled();
expect(parentController).toBeUndefined();
expect(siblingController).toBeUndefined();
element.remove();
expect(MeController.prototype.$onDestroy).toHaveBeenCalled();

});
});

Expand All @@ -5830,9 +5920,12 @@ describe('$compile', function() {
$onInit: function() {
parentController = this.container;
siblingController = this.friend;
}
},
$onDestroy: function() {}
};
spyOn(meController, '$onInit').andCallThrough();
spyOn(meController, '$onDestroy').andCallThrough();

return meController;
}

Expand Down Expand Up @@ -5864,8 +5957,11 @@ describe('$compile', function() {
inject(function($compile, $rootScope, meDirective) {
element = $compile('<parent><me sibling></me></parent>')($rootScope);
expect(meController.$onInit).toHaveBeenCalled();
expect(meController.$onDestroy).not.toHaveBeenCalled();
expect(parentController).toEqual(jasmine.any(ParentController));
expect(siblingController).toEqual(jasmine.any(SiblingController));
element.remove();
expect(meController.$onDestroy).toHaveBeenCalled();
});
});

Expand Down
0