Permalink
Browse files

feat(ngClass): add support for conditional map within an array.

This change allows `ngClass` expressions to have both objects and strings
within an array:

```js
$scope.classVar = 'nav-item';
$scope.activeVar = true;
```

```html
<div ng-class=" [classVar, {'is-active': activeVar }] ">
```

In this case, the CSS classes that will be added are: 'nav-item' and 'is-active'.

Closes #4807
  • Loading branch information...
bfanger authored and petebacondarwin committed Mar 18, 2015
1 parent ea9fd82 commit 4588e627bb7238b2113241919b948d0e5166c76d
Showing with 41 additions and 11 deletions.
  1. +30 −11 src/ng/directive/ngClass.js
  2. +11 −0 test/ng/directive/ngClassSpec.js
@@ -96,12 +96,15 @@ function classDirective(name, selector) {
}
function arrayClasses(classVal) {
var classes = [];
if (isArray(classVal)) {
return classVal.join(' ').split(' ');
forEach(classVal, function(v) {
classes = classes.concat(arrayClasses(v));
});
return classes;
} else if (isString(classVal)) {
return classVal.split(' ');
} else if (isObject(classVal)) {
var classes = [];
forEach(classVal, function(v, k) {
if (v) {
classes = classes.concat(k.split(' '));
@@ -129,16 +132,18 @@ function classDirective(name, selector) {
* 1. If the expression evaluates to a string, the string should be one or more space-delimited class
* names.
*
* 2. If the expression evaluates to an array, each element of the array should be a string that is
* one or more space-delimited class names.
*
* 3. If the expression evaluates to an object, then for each key-value pair of the
* 2. If the expression evaluates to an object, then for each key-value pair of the
* object with a truthy value the corresponding key is used as a class name.
*
* 3. If the expression evaluates to an array, each element of the array should either be a string as in
* type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
* to give you more control over what CSS classes appear. See the code below for an example of this.
*
*
* The directive won't add duplicate classes if a particular class was already set.
*
* When the expression changes, the previously added classes are removed and only then the
* new classes are added.
* When the expression changes, the previously added classes are removed and only then are the
* new classes added.
*
* @animations
* **add** - happens just before the class is applied to the elements
@@ -167,17 +172,24 @@ function classDirective(name, selector) {
<input ng-model="style1" placeholder="Type: bold, strike or red"><br>
<input ng-model="style2" placeholder="Type: bold, strike or red"><br>
<input ng-model="style3" placeholder="Type: bold, strike or red"><br>
<hr>
<p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
<input ng-model="style4" placeholder="Type: bold, strike"><br>
<input type="checkbox" ng-model="warning"> warning (apply "orange" class)
</file>
<file name="style.css">
.strike {
text-decoration: line-through;
text-decoration: line-through;
}
.bold {
font-weight: bold;
}
.red {
color: red;
}
.orange {
color: orange;

This comment has been minimized.

Show comment
Hide comment
@gkalpak

gkalpak Mar 20, 2015

Member

👍 for orange color :)

@gkalpak

gkalpak Mar 20, 2015

Member

👍 for orange color :)

}
</file>
<file name="protractor.js" type="protractor">
var ps = element.all(by.css('p'));
@@ -202,11 +214,18 @@ function classDirective(name, selector) {
});
it('array example should have 3 classes', function() {
expect(ps.last().getAttribute('class')).toBe('');
expect(ps.get(2).getAttribute('class')).toBe('');
element(by.model('style1')).sendKeys('bold');
element(by.model('style2')).sendKeys('strike');
element(by.model('style3')).sendKeys('red');
expect(ps.last().getAttribute('class')).toBe('bold strike red');
expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
});
it('array with map example should have 2 classes', function() {
expect(ps.last().getAttribute('class')).toBe('');
element(by.model('style4')).sendKeys('bold');
element(by.model('warning')).click();
expect(ps.last().getAttribute('class')).toBe('bold orange');
});
</file>
</example>
@@ -63,6 +63,17 @@ describe('ngClass', function() {
expect(element.hasClass('AnotB')).toBeFalsy();
}));
it('should support adding multiple classes via an array mixed with conditionally via a map', inject(function($rootScope, $compile) {
element = $compile('<div class="existing" ng-class="[\'A\', {\'B\': condition}]"></div>')($rootScope);
$rootScope.$digest();
expect(element.hasClass('existing')).toBeTruthy();
expect(element.hasClass('A')).toBeTruthy();
expect(element.hasClass('B')).toBeFalsy();
$rootScope.condition = true;
$rootScope.$digest();
expect(element.hasClass('B')).toBeTruthy();
}));
it('should remove classes when the referenced object is the same but its property is changed',
inject(function($rootScope, $compile) {

3 comments on commit 4588e62

@osi-oswald

This comment has been minimized.

Show comment
Hide comment
@osi-oswald

osi-oswald Mar 26, 2015

This is fantastic! When will this be available? ;-)

This is fantastic! When will this be available? ;-)

@petebacondarwin

This comment has been minimized.

Show comment
Hide comment
@petebacondarwin

petebacondarwin Mar 26, 2015

Member

In the next release 1.4.0-beta.7

Member

petebacondarwin replied Mar 26, 2015

In the next release 1.4.0-beta.7

@alydemah

This comment has been minimized.

Show comment
Hide comment
@alydemah

alydemah Aug 3, 2018

Working on old project, 2018 here and this was really cool. now time to implement

Working on old project, 2018 here and this was really cool. now time to implement

Please sign in to comment.