Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

fix(input): add support for dynamically loaded ngMessage directives. #8387

Closed
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
41 changes: 34 additions & 7 deletions src/components/input/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -717,16 +717,43 @@ function ngMessageDirective($mdUtil) {
priority: 100
};

function compile(element) {
var inputContainer = $mdUtil.getClosest(element, "md-input-container");
function compile(tElement) {
if (!isInsideInputContainer(tElement)) {

// When the current element is inside of a document fragment, then we need to check for an input-container
// in the postLink, because the element will be later added to the DOM and is currently just in a temporary
// fragment, which causes the input-container check to fail.
if (isInsideFragment()) {
return function (scope, element) {
if (isInsideInputContainer(element)) {
// Inside of the postLink function, a ngMessage directive will be a comment element, because it's
// currently hidden. To access the shown element, we need to use the element from the compile function.
initMessageElement(tElement);
}
};
}
} else {
initMessageElement(tElement);
}

// If we are not a child of an input container, don't do anything
if (!inputContainer) return;
function isInsideFragment() {
var nextNode = tElement[0];
while (nextNode = nextNode.parentNode) {
if (nextNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
return true;
}
}
return false;
}

// Add our animation class
element.toggleClass('md-input-message-animation', true);
function isInsideInputContainer(element) {
return !!$mdUtil.getClosest(element, "md-input-container");
}

return {};
function initMessageElement(element) {
// Add our animation class
element.toggleClass('md-input-message-animation', true);
}
}
}

Expand Down
53 changes: 53 additions & 0 deletions src/components/input/input.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,59 @@ describe('md-input-container directive', function() {
expect(el[0].querySelector("[ng-messages]").classList.contains('md-auto-hide')).toBe(false);
}));

it('should set the animation class on the ngMessage properly', inject(function() {
var element = compile(
'<md-input-container>' +
'<input ng-model="inputVal">' +
'<div ng-messages>' +
'<ng-message id="requiredMessage" when="required">Field required</ng-message>' +
'</div>' +
'</md-input-container>'
);

var ngMessage = element.find('ng-message');
expect(ngMessage).toHaveClass('md-input-message-animation');
}));

it('should set the animation class on a transcluded ngMessage', function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm curious if we could do a different/shorter test here.

Based on the issues this is supposed to fix, if we put it inside an autocomplete, or use the ng-messages-include directive, it would fail correct (I mean, without this PR)?

Is there a reason to do it as a document fragment test rather than one of the standard transclusion methods listed in the issues?

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, as I commented on the test, using the document fragment is testing the new added code in the input component.

Using a tranclusion here and testing it, would be more complicated.

I will give it a try with the ng-messages-include.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, if that's the case, then go ahead and add the ng-messages-include as a separate test if you can 😄

Thanks for the clarification!

// We can emulate the transclusion, by wrapping the ngMessage inside of a document fragment.
// It is not necessary to add a *extra* component / directive for that, since we just
// want to the test the DocumentFragment detection.
var fragment = document.createDocumentFragment();

var inputContainer = compile(
'<md-input-container>' +
'<input ng-model="inputVal">' +
'<div ng-messages id="messageInsertion">' +
'</div>' +
'</md-input-container>'
);

// We build our element, without compiling and linking it.
// Because we invoke those steps manually during the tests.
var messageElement = angular.element(
'<ng-message id="requiredMessage" when="required">Field Required</ng-message>'
);

fragment.appendChild(messageElement[0]);

// Only compile the element at this time, and link it to its scope later.
// Normally the directive will add the animation class upon compile.
var linkFn = $compile(messageElement);

expect(messageElement).not.toHaveClass('md-input-message-animation');

// Now we emulate the finish of the transclusion.
// We move the element from the fragment into the correct input
// container.
inputContainer[0].appendChild(messageElement[0]);

// Manually invoke the postLink function of the directive.
linkFn($rootScope.$new());

expect(messageElement).toHaveClass('md-input-message-animation');
});

it('should select the input value on focus', inject(function($timeout) {
var container = setup('md-select-on-focus');
var input = container.find('input');
Expand Down