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

Commit 2b046f9

Browse files
feat(ngModel): provide ng-empty and ng-not-empty CSS classes
If the `$viewValue` is empty then the `ng-empty` CSS class is applied to the input. Conversely, if it is not empty the `ng-not-empty` CSS class is applied. Emptiness is ascertained by calling `NgModelController.$isEmpty()` Closes #10050
1 parent da6282f commit 2b046f9

File tree

3 files changed

+48
-5
lines changed

3 files changed

+48
-5
lines changed

src/ng/directive/ngModel.js

+22-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ var VALID_CLASS = 'ng-valid',
1414
DIRTY_CLASS = 'ng-dirty',
1515
UNTOUCHED_CLASS = 'ng-untouched',
1616
TOUCHED_CLASS = 'ng-touched',
17-
PENDING_CLASS = 'ng-pending';
17+
PENDING_CLASS = 'ng-pending',
18+
EMPTY_CLASS = 'ng-empty',
19+
NOT_EMPTY_CLASS = 'ng-not-empty';
1820

1921
var ngModelMinErr = minErr('ngModel');
2022

@@ -316,6 +318,17 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
316318
return isUndefined(value) || value === '' || value === null || value !== value;
317319
};
318320

321+
this.$$updateEmptyClasses = function(value) {
322+
if (this.$isEmpty(value)) {
323+
$animate.removeClass($element, NOT_EMPTY_CLASS);
324+
$animate.addClass($element, EMPTY_CLASS);
325+
} else {
326+
$animate.removeClass($element, EMPTY_CLASS);
327+
$animate.addClass($element, NOT_EMPTY_CLASS);
328+
}
329+
};
330+
331+
319332
var currentValidationRunId = 0;
320333

321334
/**
@@ -652,6 +665,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
652665
if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
653666
return;
654667
}
668+
ctrl.$$updateEmptyClasses(viewValue);
655669
ctrl.$$lastCommittedViewValue = viewValue;
656670

657671
// change to dirty
@@ -834,6 +848,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
834848
viewValue = formatters[idx](viewValue);
835849
}
836850
if (ctrl.$viewValue !== viewValue) {
851+
ctrl.$$updateEmptyClasses(viewValue);
837852
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
838853
ctrl.$render();
839854

@@ -864,7 +879,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
864879
* require.
865880
* - Providing validation behavior (i.e. required, number, email, url).
866881
* - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
867-
* - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
882+
* - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`,
883+
* `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations.
868884
* - Registering the control with its parent {@link ng.directive:form form}.
869885
*
870886
* Note: `ngModel` will try to bind to the property given by evaluating the expression on the
@@ -905,13 +921,16 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
905921
* - `ng-touched`: the control has been blurred
906922
* - `ng-untouched`: the control hasn't been blurred
907923
* - `ng-pending`: any `$asyncValidators` are unfulfilled
924+
* - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined
925+
* by the {@link ngModel.NgModelController#$isEmpty} method
926+
* - `ng-not-empty`: the view contains a non-empty value
908927
*
909928
* Keep in mind that ngAnimate can detect each of these classes when added and removed.
910929
*
911930
* ## Animation Hooks
912931
*
913932
* Animations within models are triggered when any of the associated CSS classes are added and removed
914-
* on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
933+
* on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`,
915934
* `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
916935
* The animations that are triggered within ngModel are similar to how they work in ngClass and
917936
* animations can be hooked into using CSS transitions, keyframes as well as JS animations.

test/helpers/matchers.js

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ beforeEach(function() {
3737
}
3838

3939
this.addMatchers({
40+
toBeEmpty: cssMatcher('ng-empty', 'ng-not-empty'),
41+
toBeNotEmpty: cssMatcher('ng-not-empty', 'ng-empty'),
4042
toBeInvalid: cssMatcher('ng-invalid', 'ng-valid'),
4143
toBeValid: cssMatcher('ng-valid', 'ng-invalid'),
4244
toBeDirty: cssMatcher('ng-dirty', 'ng-pristine'),

test/ng/directive/ngModelSpec.js

+24-2
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,26 @@ describe('ngModel', function() {
13281328
describe('CSS classes', function() {
13291329
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
13301330

1331+
it('should set ng-empty or ng-not-empty the view value changes', inject(function($compile, $rootScope, $sniffer) {
1332+
var element = $compile('<input ng-model="value" />')($rootScope);
1333+
1334+
$rootScope.$digest();
1335+
expect(element).toBeEmpty();
1336+
1337+
$rootScope.value = 'XXX';
1338+
$rootScope.$digest();
1339+
expect(element).toBeNotEmpty();
1340+
1341+
element.val('');
1342+
browserTrigger(element, $sniffer.hasEvent('input') ? 'input' : 'change');
1343+
expect(element).toBeEmpty();
1344+
1345+
element.val('YYY');
1346+
browserTrigger(element, $sniffer.hasEvent('input') ? 'input' : 'change');
1347+
expect(element).toBeNotEmpty();
1348+
}));
1349+
1350+
13311351
it('should set css classes (ng-valid, ng-invalid, ng-pristine, ng-dirty, ng-untouched, ng-touched)',
13321352
inject(function($compile, $rootScope, $sniffer) {
13331353
var element = $compile('<input type="email" ng-model="value" />')($rootScope);
@@ -1693,8 +1713,10 @@ describe('ngModel', function() {
16931713
model.$setViewValue('some dirty value');
16941714

16951715
var animations = findElementAnimations(input, $animate.queue);
1696-
assertValidAnimation(animations[0], 'removeClass', 'ng-pristine');
1697-
assertValidAnimation(animations[1], 'addClass', 'ng-dirty');
1716+
assertValidAnimation(animations[0], 'removeClass', 'ng-empty');
1717+
assertValidAnimation(animations[1], 'addClass', 'ng-not-empty');
1718+
assertValidAnimation(animations[2], 'removeClass', 'ng-pristine');
1719+
assertValidAnimation(animations[3], 'addClass', 'ng-dirty');
16981720
}));
16991721

17001722

0 commit comments

Comments
 (0)