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

ngMessages leak #16406

Merged
merged 1 commit into from
Jan 17, 2018
Merged
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
20 changes: 13 additions & 7 deletions src/ngMessages/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -417,13 +417,6 @@ angular.module('ngMessages', [], function initAngularHelpers() {

$scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);

// If the element is destroyed, proactively destroy all the currently visible messages
$element.on('$destroy', function() {
forEach(messages, function(item) {
item.message.detach();
});
});

this.reRender = function() {
if (!renderLater) {
renderLater = true;
Expand Down Expand Up @@ -498,6 +491,9 @@ angular.module('ngMessages', [], function initAngularHelpers() {
function removeMessageNode(parent, comment, key) {
var messageNode = messages[key];

// This message node may have already been removed by a call to deregister()
if (!messageNode) return;

var match = findPreviousMessage(parent, comment);
if (match) {
match.next = messageNode.next;
Expand Down Expand Up @@ -702,6 +698,8 @@ function ngMessageDirectiveFactory() {
// by another structural directive then it's time
// to deregister the message from the controller
currentElement.on('$destroy', function() {
// If the message element was removed via a call to `detach` then `currentElement` will be null
// So this handler only handles cases where something else removed the message element.
if (currentElement && currentElement.$$attachId === $$attachId) {
ngMessagesCtrl.deregister(commentNode);
messageCtrl.detach();
Expand All @@ -719,6 +717,14 @@ function ngMessageDirectiveFactory() {
}
}
});

// We need to ensure that this directive deregisters itself when it no longer exists
// Normally this is done when the attached element is destroyed; but if this directive
// gets removed before we attach the message to the DOM there is nothing to watch
// in which case we must deregister when the containing scope is destroyed.
scope.$on('$destroy', function() {
ngMessagesCtrl.deregister(commentNode);
});
}
};
}];
Expand Down
24 changes: 24 additions & 0 deletions test/ngMessages/messagesSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,30 @@ describe('ngMessages', function() {
})
);

it('should unregister the ngMessage even if it was never attached',
inject(function($compile, $rootScope) {
var html =
'<div ng-messages="items">' +
'<div ng-if="show"><div ng-message="x">ERROR</div></div>' +
'</div>';

element = $compile(html)($rootScope);
Copy link
Contributor

Choose a reason for hiding this comment

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

There could be an expectation here that checks that the messge isn't initially attached / in the DOM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added


var ctrl = element.controller('ngMessages');

expect(messageChildren(element).length).toBe(0);
expect(Object.keys(ctrl.messages).length).toEqual(0);

$rootScope.$apply('show = true');
expect(messageChildren(element).length).toBe(0);
expect(Object.keys(ctrl.messages).length).toEqual(1);

$rootScope.$apply('show = false');
expect(messageChildren(element).length).toBe(0);
expect(Object.keys(ctrl.messages).length).toEqual(0);
})
);


describe('when including templates', function() {
they('should work with a dynamic collection model which is managed by ngRepeat',
Expand Down