@@ -14,9 +14,16 @@ function classDirective(name, selector) {
14
14
return {
15
15
restrict : 'AC' ,
16
16
link : function ( scope , element , attr ) {
17
+ var expression = attr [ name ] . trim ( ) ;
18
+ var isOneTime = ( expression . charAt ( 0 ) === ':' ) && ( expression . charAt ( 1 ) === ':' ) ;
19
+
20
+ var watchInterceptor = isOneTime ? toFlatValue : toClassString ;
21
+ var watchExpression = $parse ( expression , watchInterceptor ) ;
22
+ var watchAction = isOneTime ? ngClassOneTimeWatchAction : ngClassWatchAction ;
23
+
17
24
var classCounts = element . data ( '$classCounts' ) ;
18
25
var oldModulo = true ;
19
- var oldVal ;
26
+ var oldClassString ;
20
27
21
28
if ( ! classCounts ) {
22
29
// Use createMap() to prevent class assumptions involving property
@@ -33,34 +40,39 @@ function classDirective(name, selector) {
33
40
} ) ;
34
41
}
35
42
36
- scope . $watch ( indexWatchExpression , function ( newModulo ) {
37
- var classes = arrayClasses ( oldVal ) ;
38
- if ( newModulo === selector ) {
39
- addClasses ( classes ) ;
40
- } else {
41
- removeClasses ( classes ) ;
42
- }
43
-
44
- oldModulo = newModulo ;
45
- } ) ;
43
+ scope . $watch ( indexWatchExpression , ngClassIndexWatchAction ) ;
46
44
}
47
45
48
- scope . $watch ( attr [ name ] , ngClassWatchAction , true ) ;
46
+ scope . $watch ( watchExpression , watchAction , isOneTime ) ;
49
47
50
- function addClasses ( classes ) {
51
- var newClasses = digestClassCounts ( classes , 1 ) ;
52
- attr . $addClass ( newClasses ) ;
48
+ function addClasses ( classString ) {
49
+ classString = digestClassCounts ( split ( classString ) , 1 ) ;
50
+ attr . $addClass ( classString ) ;
53
51
}
54
52
55
- function removeClasses ( classes ) {
56
- var newClasses = digestClassCounts ( classes , - 1 ) ;
57
- attr . $removeClass ( newClasses ) ;
53
+ function removeClasses ( classString ) {
54
+ classString = digestClassCounts ( split ( classString ) , - 1 ) ;
55
+ attr . $removeClass ( classString ) ;
56
+ }
57
+
58
+ function updateClasses ( oldClassString , newClassString ) {
59
+ var oldClassArray = split ( oldClassString ) ;
60
+ var newClassArray = split ( newClassString ) ;
61
+
62
+ var toRemoveArray = arrayDifference ( oldClassArray , newClassArray ) ;
63
+ var toAddArray = arrayDifference ( newClassArray , oldClassArray ) ;
64
+
65
+ var toRemoveString = digestClassCounts ( toRemoveArray , - 1 ) ;
66
+ var toAddString = digestClassCounts ( toAddArray , 1 ) ;
67
+
68
+ attr . $addClass ( toAddString ) ;
69
+ attr . $removeClass ( toRemoveString ) ;
58
70
}
59
71
60
- function digestClassCounts ( classes , count ) {
72
+ function digestClassCounts ( classArray , count ) {
61
73
var classesToUpdate = [ ] ;
62
74
63
- forEach ( classes , function ( className ) {
75
+ forEach ( classArray , function ( className ) {
64
76
if ( count > 0 || classCounts [ className ] ) {
65
77
classCounts [ className ] = ( classCounts [ className ] || 0 ) + count ;
66
78
if ( classCounts [ className ] === + ( count > 0 ) ) {
@@ -72,31 +84,33 @@ function classDirective(name, selector) {
72
84
return classesToUpdate . join ( ' ' ) ;
73
85
}
74
86
75
- function updateClasses ( oldClasses , newClasses ) {
76
- var toAdd = arrayDifference ( newClasses , oldClasses ) ;
77
- var toRemove = arrayDifference ( oldClasses , newClasses ) ;
78
- toAdd = digestClassCounts ( toAdd , 1 ) ;
79
- toRemove = digestClassCounts ( toRemove , - 1 ) ;
87
+ function ngClassIndexWatchAction ( newModulo ) {
88
+ // This watch-action should run before the `ngClass[OneTime]WatchAction()`, thus it
89
+ // adds/removes `oldClassString`. If the `ngClass` expression has changed as well, the
90
+ // `ngClass[OneTime]WatchAction()` will update the classes.
91
+ if ( newModulo === selector ) {
92
+ addClasses ( oldClassString ) ;
93
+ } else {
94
+ removeClasses ( oldClassString ) ;
95
+ }
80
96
81
- attr . $addClass ( toAdd ) ;
82
- attr . $removeClass ( toRemove ) ;
97
+ oldModulo = newModulo ;
83
98
}
84
99
85
- function ngClassWatchAction ( newVal ) {
86
- if ( oldModulo === selector ) {
87
- var newClasses = arrayClasses ( newVal || [ ] ) ;
88
- if ( ! oldVal ) {
89
- addClasses ( newClasses ) ;
90
- } else if ( ! equals ( newVal , oldVal ) ) {
91
- var oldClasses = arrayClasses ( oldVal ) ;
92
- updateClasses ( oldClasses , newClasses ) ;
93
- }
100
+ function ngClassOneTimeWatchAction ( newClassValue ) {
101
+ var newClassString = toClassString ( newClassValue ) ;
102
+
103
+ if ( newClassString !== oldClassString ) {
104
+ ngClassWatchAction ( newClassString ) ;
94
105
}
95
- if ( isArray ( newVal ) ) {
96
- oldVal = newVal . map ( function ( v ) { return shallowCopy ( v ) ; } ) ;
97
- } else {
98
- oldVal = shallowCopy ( newVal ) ;
106
+ }
107
+
108
+ function ngClassWatchAction ( newClassString ) {
109
+ if ( oldModulo === selector ) {
110
+ updateClasses ( oldClassString , newClassString ) ;
99
111
}
112
+
113
+ oldClassString = newClassString ;
100
114
}
101
115
}
102
116
} ;
@@ -121,24 +135,50 @@ function classDirective(name, selector) {
121
135
return values ;
122
136
}
123
137
124
- function arrayClasses ( classVal ) {
125
- var classes = [ ] ;
126
- if ( isArray ( classVal ) ) {
127
- forEach ( classVal , function ( v ) {
128
- classes = classes . concat ( arrayClasses ( v ) ) ;
129
- } ) ;
130
- return classes ;
131
- } else if ( isString ( classVal ) ) {
132
- return classVal . split ( ' ' ) ;
133
- } else if ( isObject ( classVal ) ) {
134
- forEach ( classVal , function ( v , k ) {
135
- if ( v ) {
136
- classes = classes . concat ( k . split ( ' ' ) ) ;
138
+ function split ( classString ) {
139
+ return classString && classString . split ( ' ' ) ;
140
+ }
141
+
142
+ function toClassString ( classValue ) {
143
+ var classString = classValue ;
144
+
145
+ if ( isArray ( classValue ) ) {
146
+ classString = classValue . map ( toClassString ) . join ( ' ' ) ;
147
+ } else if ( isObject ( classValue ) ) {
148
+ classString = Object . keys ( classValue ) .
149
+ filter ( function ( key ) { return classValue [ key ] ; } ) .
150
+ join ( ' ' ) ;
151
+ }
152
+
153
+ return classString ;
154
+ }
155
+
156
+ function toFlatValue ( classValue ) {
157
+ var flatValue = classValue ;
158
+
159
+ if ( isArray ( classValue ) ) {
160
+ flatValue = classValue . map ( toFlatValue ) ;
161
+ } else if ( isObject ( classValue ) ) {
162
+ var hasUndefined = false ;
163
+
164
+ flatValue = Object . keys ( classValue ) . filter ( function ( key ) {
165
+ var value = classValue [ key ] ;
166
+
167
+ if ( ! hasUndefined && isUndefined ( value ) ) {
168
+ hasUndefined = true ;
137
169
}
170
+
171
+ return value ;
138
172
} ) ;
139
- return classes ;
173
+
174
+ if ( hasUndefined ) {
175
+ // Prevent the `oneTimeLiteralWatchInterceptor` from unregistering
176
+ // the watcher, by including at least one `undefined` value.
177
+ flatValue . push ( undefined ) ;
178
+ }
140
179
}
141
- return classVal ;
180
+
181
+ return flatValue ;
142
182
}
143
183
}
144
184
0 commit comments