X Tutup
Skip to content

Commit ed25a29

Browse files
fix(NgClass): take initial classes into account during cleanup
Closes #3557
1 parent a7a1851 commit ed25a29

File tree

2 files changed

+177
-55
lines changed

2 files changed

+177
-55
lines changed

modules/angular2/src/directives/ng_class.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,24 @@ import {ListWrapper, StringMapWrapper, isListLikeIterable} from 'angular2/src/fa
3333
@Directive({
3434
selector: '[ng-class]',
3535
lifecycle: [LifecycleEvent.onCheck, LifecycleEvent.onDestroy],
36-
properties: ['rawClass: ng-class']
36+
properties: ['rawClass: ng-class', 'initialClasses: class']
3737
})
3838
export class NgClass {
3939
private _differ: any;
4040
private _mode: string;
41-
_rawClass;
41+
private _initialClasses = [];
42+
private _rawClass;
4243

4344
constructor(private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
4445
private _ngEl: ElementRef, private _renderer: Renderer) {}
4546

47+
set initialClasses(v) {
48+
this._applyInitialClasses(true);
49+
this._initialClasses = isPresent(v) && isString(v) ? v.split(' ') : [];
50+
this._applyInitialClasses(false);
51+
this._applyClasses(this._rawClass, false);
52+
}
53+
4654
set rawClass(v) {
4755
this._cleanupClasses(this._rawClass);
4856

@@ -59,6 +67,8 @@ export class NgClass {
5967
this._differ = this._keyValueDiffers.find(v).create(null);
6068
this._mode = 'keyValue';
6169
}
70+
} else {
71+
this._differ = null;
6272
}
6373
}
6474

@@ -78,15 +88,8 @@ export class NgClass {
7888
onDestroy(): void { this._cleanupClasses(this._rawClass); }
7989

8090
private _cleanupClasses(rawClassVal): void {
81-
if (isPresent(rawClassVal)) {
82-
if (isListLikeIterable(rawClassVal)) {
83-
ListWrapper.forEach(rawClassVal, (className) => { this._toggleClass(className, false); });
84-
} else {
85-
StringMapWrapper.forEach(rawClassVal, (expVal, className) => {
86-
if (expVal) this._toggleClass(className, false);
87-
});
88-
}
89-
}
91+
this._applyClasses(rawClassVal, true);
92+
this._applyInitialClasses(false);
9093
}
9194

9295
private _applyKeyValueChanges(changes: any): void {
@@ -104,6 +107,23 @@ export class NgClass {
104107
changes.forEachRemovedItem((record) => { this._toggleClass(record.item, false); });
105108
}
106109

110+
private _applyInitialClasses(isCleanup: boolean) {
111+
ListWrapper.forEach(this._initialClasses,
112+
(className) => { this._toggleClass(className, !isCleanup); });
113+
}
114+
115+
private _applyClasses(rawClassVal, isCleanup: boolean) {
116+
if (isPresent(rawClassVal)) {
117+
if (isListLikeIterable(rawClassVal)) {
118+
ListWrapper.forEach(rawClassVal, (className) => this._toggleClass(className, !isCleanup));
119+
} else {
120+
StringMapWrapper.forEach(rawClassVal, (expVal, className) => {
121+
if (expVal) this._toggleClass(className, !isCleanup);
122+
});
123+
}
124+
}
125+
}
126+
107127
private _toggleClass(className: string, enabled): void {
108128
this._renderer.setElementClass(this._ngEl, className, enabled);
109129
}

modules/angular2/test/directives/ng_class_spec.ts

Lines changed: 146 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,25 @@ export function main() {
127127
rootTC.componentInstance.objExpr = {baz: true};
128128
detectChangesAndCheck(rootTC, 'ng-binding baz');
129129

130+
async.done();
131+
});
132+
}));
133+
134+
it('should remove active classes when expression evaluates to null',
135+
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
136+
var template = '<div [ng-class]="objExpr"></div>';
137+
138+
tcb.overrideTemplate(TestComponent, template)
139+
.createAsync(TestComponent)
140+
.then((rootTC) => {
141+
detectChangesAndCheck(rootTC, 'ng-binding foo');
142+
143+
rootTC.componentInstance.objExpr = null;
144+
detectChangesAndCheck(rootTC, 'ng-binding');
145+
146+
rootTC.componentInstance.objExpr = {'foo': false, 'bar': true};
147+
detectChangesAndCheck(rootTC, 'ng-binding bar');
148+
130149
async.done();
131150
});
132151
}));
@@ -181,6 +200,22 @@ export function main() {
181200
rootTC.componentInstance.arrExpr = ['bar'];
182201
detectChangesAndCheck(rootTC, 'ng-binding bar');
183202

203+
async.done();
204+
});
205+
}));
206+
207+
it('should take initial classes into account when a reference changes',
208+
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
209+
var template = '<div class="foo" [ng-class]="arrExpr"></div>';
210+
211+
tcb.overrideTemplate(TestComponent, template)
212+
.createAsync(TestComponent)
213+
.then((rootTC) => {
214+
detectChangesAndCheck(rootTC, 'foo ng-binding');
215+
216+
rootTC.componentInstance.arrExpr = ['bar'];
217+
detectChangesAndCheck(rootTC, 'ng-binding foo bar');
218+
184219
async.done();
185220
});
186221
}));
@@ -220,7 +255,6 @@ export function main() {
220255
});
221256
}));
222257

223-
224258
it('should remove active classes when switching from string to null',
225259
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
226260
var template = `<div [ng-class]="strExpr"></div>`;
@@ -236,65 +270,133 @@ export function main() {
236270
async.done();
237271
});
238272
}));
273+
274+
it('should take initial classes into account when switching from string to null',
275+
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
276+
var template = `<div class="foo" [ng-class]="strExpr"></div>`;
277+
278+
tcb.overrideTemplate(TestComponent, template)
279+
.createAsync(TestComponent)
280+
.then((rootTC) => {
281+
detectChangesAndCheck(rootTC, 'foo ng-binding');
282+
283+
rootTC.componentInstance.strExpr = null;
284+
detectChangesAndCheck(rootTC, 'ng-binding foo');
285+
286+
async.done();
287+
});
288+
}));
289+
239290
});
240291

241-
it('should remove active classes when expression evaluates to null',
242-
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
243-
var template = '<div [ng-class]="objExpr"></div>';
292+
describe('cooperation with other class-changing constructs', () => {
244293

245-
tcb.overrideTemplate(TestComponent, template)
246-
.createAsync(TestComponent)
247-
.then((rootTC) => {
248-
detectChangesAndCheck(rootTC, 'ng-binding foo');
294+
it('should co-operate with the class attribute',
295+
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
296+
var template = '<div [ng-class]="objExpr" class="init foo"></div>';
249297

250-
rootTC.componentInstance.objExpr = null;
251-
detectChangesAndCheck(rootTC, 'ng-binding');
298+
tcb.overrideTemplate(TestComponent, template)
299+
.createAsync(TestComponent)
300+
.then((rootTC) => {
301+
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
302+
detectChangesAndCheck(rootTC, 'init foo ng-binding bar');
252303

253-
rootTC.componentInstance.objExpr = {'foo': false, 'bar': true};
254-
detectChangesAndCheck(rootTC, 'ng-binding bar');
304+
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'foo', false);
305+
detectChangesAndCheck(rootTC, 'init ng-binding bar');
255306

256-
async.done();
257-
});
258-
}));
307+
rootTC.componentInstance.objExpr = null;
308+
detectChangesAndCheck(rootTC, 'init ng-binding foo');
259309

260-
it('should co-operate with the class attribute',
261-
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
262-
var template = '<div [ng-class]="objExpr" class="init foo"></div>';
310+
async.done();
311+
});
312+
}));
313+
314+
it('should co-operate with the interpolated class attribute',
315+
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
316+
var template = `<div [ng-class]="objExpr" class="{{'init foo'}}"></div>`;
317+
318+
tcb.overrideTemplate(TestComponent, template)
319+
.createAsync(TestComponent)
320+
.then((rootTC) => {
321+
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
322+
detectChangesAndCheck(rootTC, `{{'init foo'}} ng-binding init foo bar`);
323+
324+
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'foo', false);
325+
detectChangesAndCheck(rootTC, `{{'init foo'}} ng-binding init bar`);
326+
327+
rootTC.componentInstance.objExpr = null;
328+
detectChangesAndCheck(rootTC, `{{'init foo'}} ng-binding init foo`);
329+
330+
async.done();
331+
});
332+
}));
333+
334+
it('should co-operate with the class attribute and binding to it',
335+
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
336+
var template = `<div [ng-class]="objExpr" class="init" [class]="'foo'"></div>`;
337+
338+
tcb.overrideTemplate(TestComponent, template)
339+
.createAsync(TestComponent)
340+
.then((rootTC) => {
341+
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
342+
detectChangesAndCheck(rootTC, `init ng-binding foo bar`);
343+
344+
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'foo', false);
345+
detectChangesAndCheck(rootTC, `init ng-binding bar`);
346+
347+
rootTC.componentInstance.objExpr = null;
348+
detectChangesAndCheck(rootTC, `init ng-binding foo`);
349+
350+
async.done();
351+
});
352+
}));
263353

264-
tcb.overrideTemplate(TestComponent, template)
265-
.createAsync(TestComponent)
266-
.then((rootTC) => {
267-
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
268-
detectChangesAndCheck(rootTC, 'init foo ng-binding bar');
354+
it('should co-operate with the class attribute and class.name binding',
355+
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
356+
var template =
357+
'<div class="init foo" [ng-class]="objExpr" [class.baz]="condition"></div>';
269358

270-
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'foo', false);
271-
detectChangesAndCheck(rootTC, 'init ng-binding bar');
359+
tcb.overrideTemplate(TestComponent, template)
360+
.createAsync(TestComponent)
361+
.then((rootTC) => {
362+
detectChangesAndCheck(rootTC, 'init foo ng-binding baz');
272363

273-
async.done();
274-
});
275-
}));
364+
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
365+
detectChangesAndCheck(rootTC, 'init foo ng-binding baz bar');
276366

277-
it('should co-operate with the class attribute and class.name binding',
278-
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
279-
var template = '<div class="init foo" [ng-class]="objExpr" [class.baz]="condition"></div>';
367+
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'foo', false);
368+
detectChangesAndCheck(rootTC, 'init ng-binding baz bar');
280369

281-
tcb.overrideTemplate(TestComponent, template)
282-
.createAsync(TestComponent)
283-
.then((rootTC) => {
284-
detectChangesAndCheck(rootTC, 'init foo ng-binding baz');
370+
rootTC.componentInstance.condition = false;
371+
detectChangesAndCheck(rootTC, 'init ng-binding bar');
285372

286-
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
287-
detectChangesAndCheck(rootTC, 'init foo ng-binding baz bar');
373+
async.done();
374+
});
375+
}));
288376

289-
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'foo', false);
290-
detectChangesAndCheck(rootTC, 'init ng-binding baz bar');
377+
it('should co-operate with initial class and class attribute binding when binding changes',
378+
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
379+
var template = '<div class="init" [ng-class]="objExpr" [class]="strExpr"></div>';
291380

292-
rootTC.componentInstance.condition = false;
293-
detectChangesAndCheck(rootTC, 'init ng-binding bar');
381+
tcb.overrideTemplate(TestComponent, template)
382+
.createAsync(TestComponent)
383+
.then((rootTC) => {
384+
detectChangesAndCheck(rootTC, 'init ng-binding foo');
385+
386+
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
387+
detectChangesAndCheck(rootTC, 'init ng-binding foo bar');
388+
389+
rootTC.componentInstance.strExpr = 'baz';
390+
detectChangesAndCheck(rootTC, 'init ng-binding bar baz foo');
294391

295-
async.done();
296-
});
297-
}));
392+
rootTC.componentInstance.objExpr = null;
393+
detectChangesAndCheck(rootTC, 'init ng-binding baz');
394+
395+
async.done();
396+
});
397+
}));
398+
399+
});
298400
})
299401
}
300402

0 commit comments

Comments
 (0)
X Tutup