diff --git a/src/ngMessages/messages.js b/src/ngMessages/messages.js
index 14ffe00506b0..6edb638dfb82 100644
--- a/src/ngMessages/messages.js
+++ b/src/ngMessages/messages.js
@@ -21,7 +21,7 @@ var jqLite = angular.element;
* sequencing based on the order of how the messages are defined in the template.
*
* Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude`
- * `ngMessage` and `ngMessageExp` directives.
+ * `ngMessage`, `ngMessageExp` and `ngMessageDefault` directives.
*
* # Usage
* The `ngMessages` directive listens on a key/value collection which is set on the ngMessages attribute.
@@ -239,6 +239,19 @@ var jqLite = angular.element;
* .some-message.ng-leave.ng-leave-active {}
* ```
*
+ * ## Displaying a default message
+ * If the ngMessages renders no inner ngMessage directive (that is to say when the key values does not
+ * match the attribute value present on each ngMessage directive), then it will render a default message
+ * using the ngMessageDefault directive.
+ *
+ * ```html
+ *
+ *
This field is required
+ *
This field is too short
+ *
This is a default message
+ *
+ * ```
+ *
* {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate.
*/
angular.module('ngMessages', [])
@@ -260,6 +273,9 @@ angular.module('ngMessages', [])
* at a time and this depends on the prioritization of the messages within the template. (This can
* be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
*
+ * A default message can also be displayed when no `ngMessage` directive is inserted, using the
+ * `ngMessageDefault` directive.
+ *
* A remote template can also be used to promote message reusability and messages can also be
* overridden.
*
@@ -272,6 +288,7 @@ angular.module('ngMessages', [])
* ...
* ...
* ...
+ * ...
*
*
*
@@ -279,6 +296,7 @@ angular.module('ngMessages', [])
* ...
* ...
* ...
+ * ...
*
* ```
*
@@ -307,6 +325,7 @@ angular.module('ngMessages', [])
* You did not enter a field
* Your field is too short
* Your field is too long
+ * This is a default message
*
*
*
@@ -379,9 +398,17 @@ angular.module('ngMessages', [])
messageCtrl.detach();
});
- unmatchedMessages.length !== totalMessages
- ? $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS)
- : $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
+ if (unmatchedMessages.length !== totalMessages) {
+ // Unset default message if setted
+ if (ctrl.default) { ctrl.default.detach(); }
+
+ $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS);
+ } else {
+ // Set default message when no other one matched
+ if (ctrl.default) { ctrl.default.attach(); }
+
+ $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
+ }
};
$scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
@@ -397,23 +424,32 @@ angular.module('ngMessages', [])
}
};
- this.register = function(comment, messageCtrl) {
- var nextKey = latestKey.toString();
- messages[nextKey] = {
- message: messageCtrl
- };
- insertMessageNode($element[0], comment, nextKey);
- comment.$$ngMessageNode = nextKey;
- latestKey++;
+ this.register = function(comment, messageCtrl, isDefault) {
+ if (isDefault) {
+ ctrl.default = messageCtrl;
+ } else {
+ var nextKey = latestKey.toString();
+ messages[nextKey] = {
+ message: messageCtrl
+ };
+ insertMessageNode($element[0], comment, nextKey);
+ comment.$$ngMessageNode = nextKey;
+ latestKey++;
+ }
ctrl.reRender();
};
- this.deregister = function(comment) {
- var key = comment.$$ngMessageNode;
- delete comment.$$ngMessageNode;
- removeMessageNode($element[0], comment, key);
- delete messages[key];
+ this.deregister = function(comment, isDefault) {
+ if (isDefault) {
+ delete ctrl.default;
+ } else {
+ var key = comment.$$ngMessageNode;
+ delete comment.$$ngMessageNode;
+ removeMessageNode($element[0], comment, key);
+ delete messages[key];
+ }
+
ctrl.reRender();
};
@@ -594,9 +630,45 @@ angular.module('ngMessages', [])
*
* @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
*/
- .directive('ngMessageExp', ngMessageDirectiveFactory('A'));
+ .directive('ngMessageExp', ngMessageDirectiveFactory('A'))
+
+ /**
+ * @ngdoc directive
+ * @name ngMessageDefault
+ * @restrict AE
+ * @scope
+ *
+ * @description
+ * `ngMessageDefault` is a directive with the purpose to show and hide a default message.
+ * For `ngMessageDefault` to operate, no `ngMessage` inner directive should be displayed
+ * in the parent `ngMessages` directive.
+ *
+ * More information about using `ngMessageDefault` can be found in the
+ * {@link module:ngMessages `ngMessages` module documentation}.
+ *
+ * @usage
+ * ```html
+ *
+ *
+ * ...
+ * ...
+ * ...
+ *
+ *
+ *
+ *
+ * ...
+ * ...
+ * ...
+ *
+ * ```
+ *
+ * @param {expression} ngMessageDefault|when no ngMessage matches.
+ */
+ .directive('ngMessageDefault', ngMessageDirectiveFactory('AE', true));
-function ngMessageDirectiveFactory(restrict) {
+
+function ngMessageDirectiveFactory(restrict, isDefault) {
return ['$animate', function($animate) {
return {
restrict: 'AE',
@@ -604,25 +676,28 @@ function ngMessageDirectiveFactory(restrict) {
terminal: true,
require: '^^ngMessages',
link: function(scope, element, attrs, ngMessagesCtrl, $transclude) {
- var commentNode = element[0];
-
- var records;
- var staticExp = attrs.ngMessage || attrs.when;
- var dynamicExp = attrs.ngMessageExp || attrs.whenExp;
- var assignRecords = function(items) {
- records = items
- ? (isArray(items)
- ? items
- : items.split(/[\s,]+/))
- : null;
- ngMessagesCtrl.reRender();
- };
-
- if (dynamicExp) {
- assignRecords(scope.$eval(dynamicExp));
- scope.$watchCollection(dynamicExp, assignRecords);
- } else {
- assignRecords(staticExp);
+ var commentNode, records, staticExp, dynamicExp;
+
+ if (!isDefault) {
+ commentNode = element[0];
+ staticExp = attrs.ngMessage || attrs.when;
+ dynamicExp = attrs.ngMessageExp || attrs.whenExp;
+
+ var assignRecords = function(items) {
+ records = items
+ ? (isArray(items)
+ ? items
+ : items.split(/[\s,]+/))
+ : null;
+ ngMessagesCtrl.reRender();
+ };
+
+ if (dynamicExp) {
+ assignRecords(scope.$eval(dynamicExp));
+ scope.$watchCollection(dynamicExp, assignRecords);
+ } else {
+ assignRecords(staticExp);
+ }
}
var currentElement, messageCtrl;
@@ -641,7 +716,7 @@ function ngMessageDirectiveFactory(restrict) {
// to deregister the message from the controller
currentElement.on('$destroy', function() {
if (currentElement) {
- ngMessagesCtrl.deregister(commentNode);
+ ngMessagesCtrl.deregister(commentNode, isDefault);
messageCtrl.detach();
}
});
@@ -655,7 +730,7 @@ function ngMessageDirectiveFactory(restrict) {
$animate.leave(elm);
}
}
- });
+ }, isDefault);
}
};
}];
diff --git a/test/ngMessages/messagesSpec.js b/test/ngMessages/messagesSpec.js
index 037175031ded..f81cd4baadf0 100644
--- a/test/ngMessages/messagesSpec.js
+++ b/test/ngMessages/messagesSpec.js
@@ -401,6 +401,24 @@ describe('ngMessages', function() {
});
});
+ it('should render a default message when no one matched', inject(function($rootScope, $compile) {
+ element = $compile('' +
+ '
Message is set
' +
+ '
Default message is set
' +
+ '
')($rootScope);
+ $rootScope.$digest();
+
+ //expect(element.text()).not.toContain('Message is set');
+ expect(element.text()).toContain('Default message is set');
+
+ $rootScope.$apply(function() {
+ $rootScope.col = { val: true };
+ });
+
+ //expect(element.text()).toContain('Message is set');
+ expect(element.text()).not.toContain('Default message is set');
+ }));
+
describe('when including templates', function() {
they('should work with a dynamic collection model which is managed by ngRepeat',
{'