Skip to content

Commit 3d6fee2

Browse files
committed
ONGOING refactor(ngclass): drop NodeAttrs, use new change detection
Relates to dart-archive#645
1 parent 1d0739a commit 3d6fee2

File tree

1 file changed

+104
-76
lines changed

1 file changed

+104
-76
lines changed

lib/directive/ng_class.dart

Lines changed: 104 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,8 @@ part of angular.directive;
6767
map: const {'ng-class': '@valueExpression'},
6868
exportExpressionAttrs: const ['ng-class'])
6969
class NgClassDirective extends _NgClassBase {
70-
NgClassDirective(dom.Element element, Scope scope, NodeAttrs attrs,
71-
NgAnimate animate)
72-
: super(element, scope, null, attrs, animate);
70+
NgClassDirective(NgElement ngElement, Scope scope)
71+
: super(ngElement, scope);
7372
}
7473

7574
/**
@@ -103,9 +102,8 @@ class NgClassDirective extends _NgClassBase {
103102
map: const {'ng-class-odd': '@valueExpression'},
104103
exportExpressionAttrs: const ['ng-class-odd'])
105104
class NgClassOddDirective extends _NgClassBase {
106-
NgClassOddDirective(dom.Element element, Scope scope, NodeAttrs attrs,
107-
NgAnimate animate)
108-
: super(element, scope, 0, attrs, animate);
105+
NgClassOddDirective(NgElement ngElement, Scope scope)
106+
: super(ngElement, scope, 0);
109107
}
110108

111109
/**
@@ -139,95 +137,125 @@ class NgClassOddDirective extends _NgClassBase {
139137
map: const {'ng-class-even': '@valueExpression'},
140138
exportExpressionAttrs: const ['ng-class-even'])
141139
class NgClassEvenDirective extends _NgClassBase {
142-
NgClassEvenDirective(dom.Element element, Scope scope, NodeAttrs attrs,
143-
NgAnimate animate)
144-
: super(element, scope, 1, attrs, animate);
140+
NgClassEvenDirective(NgElement ngElement, Scope scope)
141+
: super(ngElement, scope, 1);
145142
}
146143

147144
abstract class _NgClassBase {
148-
final dom.Element element;
149-
final Scope scope;
150-
final int mode;
151-
final NodeAttrs nodeAttrs;
152-
final NgAnimate _animate;
153-
var previousSet = [];
154-
var currentSet = [];
155-
Watch _watch;
145+
final NgElement _ngElement;
146+
final Scope _scope;
147+
final int _mode;
148+
Watch _watchExpression;
149+
Watch _watchPosition;
150+
Set<String> _previousSet;
151+
var _currentSet = new Set<String>();
152+
bool _first = true;
156153

157-
_NgClassBase(this.element, this.scope, this.mode, this.nodeAttrs,
158-
this._animate)
159-
{
160-
var prevClass;
161-
162-
nodeAttrs.observe('class', (String newValue) {
163-
if (prevClass != newValue) {
164-
prevClass = newValue;
165-
_handleChange(scope.context[r'$index']);
166-
}
167-
});
168-
}
154+
_NgClassBase(this._ngElement, this._scope, [this._mode = null]);
169155

170156
set valueExpression(expression) {
171-
if (_watch != null) _watch.remove();
172-
_watch = scope.watch(expression, (current, _) {
173-
currentSet = _flatten(current);
174-
_handleChange(scope.context[r'$index']);
175-
},
176-
canChangeModel: false,
177-
collection: true);
157+
if (_watchExpression != null) _watchExpression.remove();
158+
_watchExpression = _scope.watch(expression, (v, _) {
159+
_computeChanges(v);
160+
_applyChanges(_scope.context[r'$index']);
161+
},
162+
canChangeModel: false,
163+
collection: true);
178164

179-
if (mode != null) {
180-
scope.watch(r'$index', (index, oldIndex) {
181-
var mod = index % 2;
182-
if (oldIndex == null || mod != oldIndex % 2) {
183-
if (mod == mode) {
184-
currentSet.forEach((css) => _animate.addClass(element, css));
165+
if (_mode != null) {
166+
if (_watchPosition != null) _watchPosition.remove();
167+
_watchPosition = _scope.watch(r'$index', (idx, previousIdx) {
168+
var mod = idx % 2;
169+
if (previousIdx == null || mod != previousIdx % 2) {
170+
if (mod == _mode) {
171+
_currentSet.forEach((cls) => _ngElement.addClass(cls));
185172
} else {
186-
previousSet.forEach((css) => _animate.removeClass(element, css));
173+
_previousSet.forEach((cls) => _ngElement.removeClass(cls));
187174
}
188175
}
189176
}, canChangeModel: false);
190177
}
191178
}
192179

193-
_handleChange(index) {
194-
if (mode == null || (index != null && index % 2 == mode)) {
195-
previousSet.forEach((css) {
196-
if (!currentSet.contains(css)) {
197-
_animate.removeClass(element, css);
198-
} else {
199-
element.classes.remove(css);
200-
}
201-
});
180+
void _computeChanges(value) {
181+
_previousSet = _currentSet.toSet();
202182

203-
currentSet.forEach((css) {
204-
if (!previousSet.contains(css)) {
205-
_animate.addClass(element, css);
206-
} else {
207-
element.classes.add(css);
208-
}
209-
});
183+
if (value is CollectionChangeRecord) {
184+
_computeCollectionChanges(value, _first);
185+
} else if (value is MapChangeRecord) {
186+
_computeMapChanges(value, _first);
187+
} else {
188+
if (value is String) {
189+
_currentSet..clear()..addAll(value.split(' '));
190+
} else if (value == null) {
191+
_currentSet.clear();
192+
} else {
193+
throw 'ng-class expects expression value to be List, Map or String, '
194+
'got $value';
195+
}
210196
}
211197

212-
previousSet = currentSet;
198+
_first = false;
213199
}
214200

215-
static List<String> _flatten(classes) {
216-
if (classes == null) return [];
217-
if (classes is CollectionChangeRecord) {
218-
classes = (classes as CollectionChangeRecord).iterable.toList();
219-
}
220-
if (classes is List) {
221-
return classes
222-
.where((String e) => e != null && e.isNotEmpty)
223-
.toList(growable: false);
201+
// todo(vicb) refactor once GH-774 gets fixed
202+
void _computeCollectionChanges(CollectionChangeRecord changes, bool first) {
203+
if (first) {
204+
changes.iterable.forEach((cls) {
205+
_currentSet.add(cls);
206+
});
207+
} else {
208+
changes.forEachAddition((AddedItem a) {
209+
_currentSet.add(a.item);
210+
});
211+
changes.forEachRemoval((RemovedItem r) {
212+
_currentSet.remove(r.item);
213+
});
224214
}
225-
if (classes is MapChangeRecord) classes = (classes as MapChangeRecord).map;
226-
if (classes is Map) {
227-
return classes.keys.where((key) => toBool(classes[key])).toList();
215+
}
216+
217+
// todo(vicb) refactor once GH-774 gets fixed
218+
_computeMapChanges(MapChangeRecord changes, first) {
219+
if (first) {
220+
changes.map.forEach((cls, active) {
221+
if (toBool(active)) {
222+
_currentSet.add(cls);
223+
}
224+
});
225+
} else {
226+
changes.forEachChange((ChangedKeyValue kv) {
227+
var cls = kv.key;
228+
var active = toBool(kv.currentValue);
229+
var wasActive = toBool(kv.previousValue);
230+
if (active != wasActive) {
231+
if (active) {
232+
_currentSet.add(cls);
233+
} else {
234+
_currentSet.remove(cls);
235+
}
236+
}
237+
});
238+
changes.forEachAddition((AddedKeyValue kv) {
239+
if (toBool(kv.currentValue)) {
240+
_currentSet.add(kv.key);
241+
}
242+
});
243+
changes.forEachRemoval((RemovedKeyValue kv) {
244+
if (toBool(kv.previousValue)) {
245+
_currentSet.remove(kv.key);
246+
}
247+
});
228248
}
229-
if (classes is String) return classes.split(' ');
230-
throw 'ng-class expects expression value to be List, Map or String, '
231-
'got $classes';
232249
}
233-
}
250+
251+
_applyChanges(index) {
252+
if (_mode == null || (index != null && index % 2 == _mode)) {
253+
_previousSet
254+
.where((cls) => cls != null)
255+
.forEach((cls) => _ngElement.removeClass(cls));
256+
_currentSet
257+
.where((cls) => cls != null)
258+
.forEach((cls) => _ngElement.addClass(cls));
259+
}
260+
}
261+
}

0 commit comments

Comments
 (0)