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

fix(ngMessages): prevent race condition with ngAnimate #12903

Merged
merged 1 commit into from
Sep 22, 2015
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
9 changes: 8 additions & 1 deletion src/ngMessages/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,9 @@ angular.module('ngMessages', [])
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
var ctrl = this;
var latestKey = 0;
var nextAttachId = 0;

this.getAttachId = function getAttachId() { return nextAttachId++; };

var messages = this.messages = {};
var renderLater, cachedCollection;
Expand Down Expand Up @@ -636,11 +639,15 @@ function ngMessageDirectiveFactory(restrict) {
$animate.enter(elm, null, element);
currentElement = elm;

// Each time we attach this node to a message we get a new id that we can match
// when we are destroying the node later.
var $$attachId = currentElement.$$attachId = ngMessagesCtrl.getAttachId();

// in the event that the parent element is destroyed
// by any other structural directive then it's time
// to deregister the message from the controller
currentElement.on('$destroy', function() {
if (currentElement) {
if (currentElement && currentElement.$$attachId === $$attachId) {
ngMessagesCtrl.deregister(commentNode);
messageCtrl.detach();
}
Expand Down
44 changes: 44 additions & 0 deletions test/ngMessages/messagesSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,50 @@ describe('ngMessages', function() {
expect(trim(element.text())).toEqual("Enter something");
}));

// issue #12856
it('should only detach the message object that is associated with the message node being removed',
inject(function($rootScope, $compile, $animate) {

// We are going to spy on the `leave` method to give us control over
// when the element is actually removed
spyOn($animate, 'leave');

// Create a basic ng-messages set up
element = $compile('<div ng-messages="col">' +
' <div ng-message="primary">Enter something</div>' +
'</div>')($rootScope);

// Trigger the message to be displayed
$rootScope.col = { primary: true };
$rootScope.$digest();
expect(messageChildren(element).length).toEqual(1);
var oldMessageNode = messageChildren(element)[0];

// Remove the message
$rootScope.col = { primary: undefined };
$rootScope.$digest();

// Since we have spied on the `leave` method, the message node is still in the DOM
expect($animate.leave).toHaveBeenCalledOnce();
var nodeToRemove = $animate.leave.mostRecentCall.args[0][0];
expect(nodeToRemove).toBe(oldMessageNode);
$animate.leave.reset();

// Add the message back in
$rootScope.col = { primary: true };
$rootScope.$digest();

// Simulate the animation completing on the node
jqLite(nodeToRemove).remove();

// We should not get another call to `leave`
expect($animate.leave).not.toHaveBeenCalled();

// There should only be the new message node
expect(messageChildren(element).length).toEqual(1);
var newMessageNode = messageChildren(element)[0];
expect(newMessageNode).not.toBe(oldMessageNode);
}));

it('should render animations when the active/inactive classes are added/removed', function() {
module('ngAnimate');
Expand Down