Skip to content

Commit b1b526d

Browse files
committed
refactor: replace color getters and setters with mixin
* Replaces the color getters and setters with a Mixin function (similar as for the disabled property) * This should reduce payload and also should make it way easier to maintain the color functionality.
1 parent cebb516 commit b1b526d

File tree

14 files changed

+265
-253
lines changed

14 files changed

+265
-253
lines changed

src/lib/button/button.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ describe('MdButton', () => {
3838
fixture.detectChanges();
3939
expect(buttonDebugElement.nativeElement.classList.contains('mat-accent')).toBe(true);
4040
expect(aDebugElement.nativeElement.classList.contains('mat-accent')).toBe(true);
41+
42+
testComponent.buttonColor = null;
43+
fixture.detectChanges();
44+
45+
expect(buttonDebugElement.nativeElement.classList).not.toContain('mat-accent');
46+
expect(aDebugElement.nativeElement.classList).not.toContain('mat-accent');
4147
});
4248

4349
it('should should not clear previous defined classes', () => {
@@ -59,7 +65,6 @@ describe('MdButton', () => {
5965
expect(buttonDebugElement.nativeElement.classList.contains('mat-primary')).toBe(false);
6066
expect(buttonDebugElement.nativeElement.classList.contains('mat-accent')).toBe(true);
6167
expect(buttonDebugElement.nativeElement.classList.contains('custom-class')).toBe(true);
62-
6368
});
6469

6570
// Regular button tests

src/lib/button/button.ts

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from '@angular/core';
1212
import {coerceBooleanProperty, FocusOriginMonitor, Platform} from '../core';
1313
import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled';
14+
import {IsColorable, mixinColor} from '../core/common-behaviors/color';
1415

1516

1617
// TODO(kara): Convert attribute selectors to classes when attr maps become available
@@ -71,8 +72,10 @@ export class MdMiniFabCssMatStyler {}
7172

7273

7374
// Boilerplate for applying mixins to MdButton.
74-
export class MdButtonBase { }
75-
export const _MdButtonMixinBase = mixinDisabled(MdButtonBase);
75+
export class MdButtonBase {
76+
constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {}
77+
}
78+
export const _MdButtonMixinBase = mixinColor(mixinDisabled(MdButtonBase), true);
7679

7780

7881
/**
@@ -89,13 +92,11 @@ export const _MdButtonMixinBase = mixinDisabled(MdButtonBase);
8992
},
9093
templateUrl: 'button.html',
9194
styleUrls: ['button.css'],
92-
inputs: ['disabled'],
95+
inputs: ['disabled', 'color'],
9396
encapsulation: ViewEncapsulation.None,
9497
changeDetection: ChangeDetectionStrategy.OnPush,
9598
})
96-
export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisable {
97-
private _color: string;
98-
99+
export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisable, IsColorable {
99100
/** Whether the button is round. */
100101
_isRoundButton: boolean = this._hasAttributeWithPrefix('fab', 'mini-fab');
101102

@@ -110,40 +111,18 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl
110111
get disableRipple() { return this._disableRipple; }
111112
set disableRipple(v) { this._disableRipple = coerceBooleanProperty(v); }
112113

113-
constructor(
114-
private _elementRef: ElementRef,
115-
private _renderer: Renderer2,
116-
private _platform: Platform,
117-
private _focusOriginMonitor: FocusOriginMonitor) {
118-
super();
114+
constructor(private _platform: Platform,
115+
private _focusOriginMonitor: FocusOriginMonitor,
116+
renderer: Renderer2,
117+
elementRef: ElementRef) {
118+
super(renderer, elementRef);
119119
this._focusOriginMonitor.monitor(this._elementRef.nativeElement, this._renderer, true);
120120
}
121121

122122
ngOnDestroy() {
123123
this._focusOriginMonitor.stopMonitoring(this._elementRef.nativeElement);
124124
}
125125

126-
/** The color of the button. Can be `primary`, `accent`, or `warn`. */
127-
@Input()
128-
get color(): string { return this._color; }
129-
set color(value: string) { this._updateColor(value); }
130-
131-
_updateColor(newColor: string) {
132-
this._setElementColor(this._color, false);
133-
this._setElementColor(newColor, true);
134-
this._color = newColor;
135-
}
136-
137-
_setElementColor(color: string, isAdd: boolean) {
138-
if (color != null && color != '') {
139-
if (isAdd) {
140-
this._renderer.addClass(this._getHostElement(), `mat-${color}`);
141-
} else {
142-
this._renderer.removeClass(this._getHostElement(), `mat-${color}`);
143-
}
144-
}
145-
}
146-
147126
/** Focuses the button. */
148127
focus(): void {
149128
this._getHostElement().focus();
@@ -189,18 +168,18 @@ export class MdButton extends _MdButtonMixinBase implements OnDestroy, CanDisabl
189168
'[attr.aria-disabled]': '_isAriaDisabled',
190169
'(click)': '_haltDisabledEvents($event)',
191170
},
192-
inputs: ['disabled'],
171+
inputs: ['disabled', 'color'],
193172
templateUrl: 'button.html',
194173
styleUrls: ['button.css'],
195174
encapsulation: ViewEncapsulation.None
196175
})
197176
export class MdAnchor extends MdButton {
198177
constructor(
199-
elementRef: ElementRef,
200-
renderer: Renderer2,
201178
platform: Platform,
202-
focusOriginMonitor: FocusOriginMonitor) {
203-
super(elementRef, renderer, platform, focusOriginMonitor);
179+
focusOriginMonitor: FocusOriginMonitor,
180+
elementRef: ElementRef,
181+
renderer: Renderer2) {
182+
super(platform, focusOriginMonitor, renderer, elementRef);
204183
}
205184

206185
/** @docs-private */

src/lib/checkbox/checkbox.ts

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
1717
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
1818
import {FocusOrigin, FocusOriginMonitor, MdRipple, RippleRef} from '../core';
1919
import {mixinDisabled, CanDisable} from '../core/common-behaviors/disabled';
20+
import {IsColorable, mixinColor} from '../core/common-behaviors/color';
2021

2122

2223
/** Monotonically increasing integer used to auto-generate unique ids for checkbox components. */
@@ -57,8 +58,10 @@ export class MdCheckboxChange {
5758
}
5859

5960
// Boilerplate for applying mixins to MdCheckbox.
60-
export class MdCheckboxBase { }
61-
export const _MdCheckboxMixinBase = mixinDisabled(MdCheckboxBase);
61+
export class MdCheckboxBase {
62+
constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {}
63+
}
64+
export const _MdCheckboxMixinBase = mixinColor(mixinDisabled(MdCheckboxBase));
6265

6366

6467
/**
@@ -82,12 +85,12 @@ export const _MdCheckboxMixinBase = mixinDisabled(MdCheckboxBase);
8285
'[class.mat-checkbox-label-before]': 'labelPosition == "before"',
8386
},
8487
providers: [MD_CHECKBOX_CONTROL_VALUE_ACCESSOR],
85-
inputs: ['disabled'],
88+
inputs: ['disabled', 'color'],
8689
encapsulation: ViewEncapsulation.None,
8790
changeDetection: ChangeDetectionStrategy.OnPush
8891
})
8992
export class MdCheckbox extends _MdCheckboxMixinBase
90-
implements ControlValueAccessor, AfterViewInit, OnDestroy, CanDisable {
93+
implements ControlValueAccessor, AfterViewInit, OnDestroy, IsColorable, CanDisable {
9194
/**
9295
* Attached to the aria-label attribute of the host element. In most cases, arial-labelledby will
9396
* take precedence so this may be omitted.
@@ -183,18 +186,18 @@ export class MdCheckbox extends _MdCheckboxMixinBase
183186

184187
private _indeterminate: boolean = false;
185188

186-
private _color: string;
187-
188189
private _controlValueAccessorChangeFn: (value: any) => void = (value) => {};
189190

190191
/** Reference to the focused state ripple. */
191192
private _focusRipple: RippleRef;
192193

193-
constructor(private _renderer: Renderer2,
194-
private _elementRef: ElementRef,
195-
private _changeDetectorRef: ChangeDetectorRef,
196-
private _focusOriginMonitor: FocusOriginMonitor) {
197-
super();
194+
constructor(private _changeDetectorRef: ChangeDetectorRef,
195+
private _focusOriginMonitor: FocusOriginMonitor,
196+
_renderer: Renderer2,
197+
_elementRef: ElementRef) {
198+
super(_renderer, _elementRef);
199+
200+
// By default the checkbox uses the accent color for styling.
198201
this.color = 'accent';
199202
}
200203

@@ -247,27 +250,6 @@ export class MdCheckbox extends _MdCheckboxMixinBase
247250
}
248251
}
249252

250-
/** The color of the button. Can be `primary`, `accent`, or `warn`. */
251-
@Input()
252-
get color(): string { return this._color; }
253-
set color(value: string) { this._updateColor(value); }
254-
255-
_updateColor(newColor: string) {
256-
this._setElementColor(this._color, false);
257-
this._setElementColor(newColor, true);
258-
this._color = newColor;
259-
}
260-
261-
_setElementColor(color: string, isAdd: boolean) {
262-
if (color != null && color != '') {
263-
if (isAdd) {
264-
this._renderer.addClass(this._elementRef.nativeElement, `mat-${color}`);
265-
} else {
266-
this._renderer.removeClass(this._elementRef.nativeElement, `mat-${color}`);
267-
}
268-
}
269-
}
270-
271253
_isRippleDisabled() {
272254
return this.disableRipple || this.disabled;
273255
}

src/lib/chips/chip.ts

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,27 @@ import {
1111

1212
import {Focusable} from '../core/a11y/focus-key-manager';
1313
import {coerceBooleanProperty} from '../core/coercion/boolean-property';
14+
import {IsColorable, mixinColor} from '../core/common-behaviors/color';
1415

1516
export interface MdChipEvent {
1617
chip: MdChip;
1718
}
1819

20+
// Boilerplate for applying mixins to MdChip.
21+
export class MdChipBase {
22+
constructor(public _renderer: Renderer2, public _elementRef: ElementRef) {}
23+
}
24+
export const _MdChipMixinBase = mixinColor(MdChipBase);
25+
26+
1927
/**
2028
* Material design styled Chip component. Used inside the MdChipList component.
2129
*/
2230
@Component({
2331
selector: `md-basic-chip, [md-basic-chip], md-chip, [md-chip],
2432
mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]`,
2533
template: `<ng-content></ng-content>`,
34+
inputs: ['color'],
2635
host: {
2736
'[class.mat-chip]': 'true',
2837
'tabindex': '-1',
@@ -35,17 +44,14 @@ export interface MdChipEvent {
3544
'(click)': '_handleClick($event)'
3645
}
3746
})
38-
export class MdChip implements Focusable, OnInit, OnDestroy {
47+
export class MdChip extends _MdChipMixinBase implements Focusable, OnInit, OnDestroy, IsColorable {
3948

4049
/** Whether or not the chip is disabled. Disabled chips cannot be focused. */
4150
protected _disabled: boolean = null;
4251

4352
/** Whether or not the chip is selected. */
4453
protected _selected: boolean = false;
4554

46-
/** The palette color of selected chips. */
47-
protected _color: string = 'primary';
48-
4955
/** Emitted when the chip is focused. */
5056
onFocus = new EventEmitter<MdChipEvent>();
5157

@@ -58,11 +64,15 @@ export class MdChip implements Focusable, OnInit, OnDestroy {
5864
/** Emitted when the chip is destroyed. */
5965
@Output() destroy = new EventEmitter<MdChipEvent>();
6066

61-
constructor(protected _renderer: Renderer2, protected _elementRef: ElementRef) { }
67+
constructor(renderer: Renderer2, elementRef: ElementRef) {
68+
super(renderer, elementRef);
69+
70+
// By default the chip component uses the primary color palette.
71+
this.color = 'primary';
72+
}
6273

6374
ngOnInit(): void {
6475
this._addDefaultCSSClass();
65-
this._updateColor(this._color);
6676
}
6777

6878
ngOnDestroy(): void {
@@ -108,15 +118,6 @@ export class MdChip implements Focusable, OnInit, OnDestroy {
108118
return this.selected;
109119
}
110120

111-
/** The color of the chip. Can be `primary`, `accent`, or `warn`. */
112-
@Input() get color(): string {
113-
return this._color;
114-
}
115-
116-
set color(value: string) {
117-
this._updateColor(value);
118-
}
119-
120121
/** Allows for programmatic focusing of the chip. */
121122
focus(): void {
122123
this._elementRef.nativeElement.focus();
@@ -147,22 +148,4 @@ export class MdChip implements Focusable, OnInit, OnDestroy {
147148
this._renderer.addClass(el, 'mat-basic-chip');
148149
}
149150
}
150-
151-
/** Updates the private _color variable and the native element. */
152-
private _updateColor(newColor: string) {
153-
this._setElementColor(this._color, false);
154-
this._setElementColor(newColor, true);
155-
this._color = newColor;
156-
}
157-
158-
/** Sets the mat-color on the native element. */
159-
private _setElementColor(color: string, isAdd: boolean) {
160-
if (color != null && color != '') {
161-
if (isAdd) {
162-
this._renderer.addClass(this._elementRef.nativeElement, `mat-${color}`);
163-
} else {
164-
this._renderer.removeClass(this._elementRef.nativeElement, `mat-${color}`);
165-
}
166-
}
167-
}
168151
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import {mixinColor} from './color';
2+
import {ElementRef, Renderer2} from '@angular/core';
3+
4+
describe('MixinColor', () => {
5+
6+
it('should augment an existing class with a color property', () => {
7+
const classWithColor = mixinColor(TestClass);
8+
const instance = new classWithColor();
9+
10+
expect(instance.color)
11+
.toBeFalsy('Expected the mixed-into class to have a color property');
12+
13+
instance.color = 'accent';
14+
15+
expect(instance.color)
16+
.toBe('accent', 'Expected the mixed-into class to have an updated color property');
17+
});
18+
19+
it('should remove old color classes if new color is set', () => {
20+
const classWithColor = mixinColor(TestClass);
21+
const instance = new classWithColor();
22+
23+
expect(instance.testElement.classList.length)
24+
.toBe(0, 'Expected the element to not have any classes at initialization');
25+
26+
instance.color = 'primary';
27+
28+
expect(instance.testElement.classList)
29+
.toContain('mat-primary', 'Expected the element to have the "mat-primary" class set');
30+
31+
instance.color = 'accent';
32+
33+
expect(instance.testElement.classList)
34+
.not.toContain('mat-primary', 'Expected the element to no longer have "mat-primary" set.');
35+
expect(instance.testElement.classList)
36+
.toContain('mat-accent', 'Expected the element to have the "mat-accent" class set');
37+
});
38+
39+
it('should allow updating the color to an empty value', () => {
40+
const classWithColor = mixinColor(TestClass, true);
41+
const instance = new classWithColor();
42+
43+
expect(instance.testElement.classList.length)
44+
.toBe(0, 'Expected the element to not have any classes at initialization');
45+
46+
instance.color = 'primary';
47+
48+
expect(instance.testElement.classList)
49+
.toContain('mat-primary', 'Expected the element to have the "mat-primary" class set');
50+
51+
instance.color = null;
52+
53+
expect(instance.testElement.classList.length)
54+
.toBe(0, 'Expected the element to not have any classes after the color has been set to null');
55+
});
56+
57+
});
58+
59+
class TestClass {
60+
testElement: HTMLElement = document.createElement('div');
61+
62+
/** Mock of a RendererV2 for the color mixin. */
63+
_renderer: Renderer2 = {
64+
addClass: (element: HTMLElement, className: string) => element.classList.add(className),
65+
removeClass: (element: HTMLElement, className: string) => element.classList.remove(className)
66+
} as any;
67+
68+
/** Fake instance of an ElementRef. */
69+
_elementRef = new ElementRef(this.testElement);
70+
}

0 commit comments

Comments
 (0)