Skip to content

Commit c30e234

Browse files
authored
refactor(material/button): remove mixin class usages (#27704)
Reworks the button to replace usages of mixin classes with either input transforms or host bindings. This is a test to see how breaking the approach is before we roll it out across the entire library.
1 parent 327fd2b commit c30e234

File tree

7 files changed

+52
-119
lines changed

7 files changed

+52
-119
lines changed

src/material/button/button-base.ts

Lines changed: 24 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,20 @@
77
*/
88

99
import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y';
10-
import {coerceBooleanProperty} from '@angular/cdk/coercion';
1110
import {Platform} from '@angular/cdk/platform';
1211
import {
1312
AfterViewInit,
13+
booleanAttribute,
1414
Directive,
1515
ElementRef,
1616
inject,
17+
Input,
1718
NgZone,
19+
numberAttribute,
1820
OnDestroy,
1921
OnInit,
2022
} from '@angular/core';
21-
import {
22-
CanColor,
23-
CanDisable,
24-
CanDisableRipple,
25-
MatRipple,
26-
mixinColor,
27-
mixinDisabled,
28-
mixinDisableRipple,
29-
MatRippleLoader,
30-
} from '@angular/material/core';
31-
32-
/** Inputs common to all buttons. */
33-
export const MAT_BUTTON_INPUTS = ['disabled', 'disableRipple', 'color'];
23+
import {MatRipple, MatRippleLoader} from '@angular/material/core';
3424

3525
/** Shared host configuration for all buttons */
3626
export const MAT_BUTTON_HOST = {
@@ -43,6 +33,7 @@ export const MAT_BUTTON_HOST = {
4333
// Add a class that applies to all buttons. This makes it easier to target if somebody
4434
// wants to target all Material buttons.
4535
'[class.mat-mdc-button-base]': 'true',
36+
'[class]': 'color ? "mat-" + color : ""',
4637
};
4738

4839
/** List of classes to add to buttons instances based on host attribute selector. */
@@ -77,24 +68,9 @@ const HOST_SELECTOR_MDC_CLASS_PAIR: {selector: string; mdcClasses: string[]}[] =
7768
},
7869
];
7970

80-
// Boilerplate for applying mixins to MatButton.
81-
/** @docs-private */
82-
export const _MatButtonMixin = mixinColor(
83-
mixinDisabled(
84-
mixinDisableRipple(
85-
class {
86-
constructor(public _elementRef: ElementRef) {}
87-
},
88-
),
89-
),
90-
);
91-
9271
/** Base class for all buttons. */
9372
@Directive()
94-
export class MatButtonBase
95-
extends _MatButtonMixin
96-
implements CanDisable, CanColor, CanDisableRipple, AfterViewInit, OnDestroy
97-
{
73+
export class MatButtonBase implements AfterViewInit, OnDestroy {
9874
private readonly _focusMonitor = inject(FocusMonitor);
9975

10076
/**
@@ -118,41 +94,41 @@ export class MatButtonBase
11894
this._rippleLoader?.attachRipple(this._elementRef.nativeElement, v);
11995
}
12096

121-
// We override `disableRipple` and `disabled` so we can hook into
122-
// their setters and update the ripple disabled state accordingly.
97+
/** Theme color palette of the button */
98+
@Input() color?: string | null;
12399

124100
/** Whether the ripple effect is disabled or not. */
125-
override get disableRipple(): boolean {
101+
@Input({transform: booleanAttribute})
102+
get disableRipple(): boolean {
126103
return this._disableRipple;
127104
}
128-
override set disableRipple(value: any) {
129-
this._disableRipple = coerceBooleanProperty(value);
105+
set disableRipple(value: any) {
106+
this._disableRipple = value;
130107
this._updateRippleDisabled();
131108
}
132109
private _disableRipple: boolean = false;
133110

134-
override get disabled(): boolean {
111+
@Input({transform: booleanAttribute})
112+
get disabled(): boolean {
135113
return this._disabled;
136114
}
137-
override set disabled(value: any) {
138-
this._disabled = coerceBooleanProperty(value);
115+
set disabled(value: any) {
116+
this._disabled = value;
139117
this._updateRippleDisabled();
140118
}
141119
private _disabled: boolean = false;
142120

143121
constructor(
144-
elementRef: ElementRef,
122+
public _elementRef: ElementRef,
145123
public _platform: Platform,
146124
public _ngZone: NgZone,
147125
public _animationMode?: string,
148126
) {
149-
super(elementRef);
150-
151127
this._rippleLoader?.configureRipple(this._elementRef.nativeElement, {
152128
className: 'mat-mdc-button-ripple',
153129
});
154130

155-
const classList = (elementRef.nativeElement as HTMLElement).classList;
131+
const classList = (_elementRef.nativeElement as HTMLElement).classList;
156132

157133
// For each of the variant selectors that is present in the button's host
158134
// attributes, add the correct corresponding MDC classes.
@@ -195,9 +171,6 @@ export class MatButtonBase
195171
}
196172
}
197173

198-
/** Shared inputs by buttons using the `<a>` tag */
199-
export const MAT_ANCHOR_INPUTS = ['disabled', 'disableRipple', 'color', 'tabIndex'];
200-
201174
/** Shared host configuration for buttons using the `<a>` tag. */
202175
export const MAT_ANCHOR_HOST = {
203176
'[attr.disabled]': 'disabled || null',
@@ -215,13 +188,19 @@ export const MAT_ANCHOR_HOST = {
215188
// Add a class that applies to all buttons. This makes it easier to target if somebody
216189
// wants to target all Material buttons.
217190
'[class.mat-mdc-button-base]': 'true',
191+
'[class]': 'color ? "mat-" + color : ""',
218192
};
219193

220194
/**
221195
* Anchor button base.
222196
*/
223197
@Directive()
224198
export class MatAnchorBase extends MatButtonBase implements OnInit, OnDestroy {
199+
@Input({
200+
transform: (value: unknown) => {
201+
return value == null ? undefined : numberAttribute(value);
202+
},
203+
})
225204
tabIndex: number;
226205

227206
constructor(elementRef: ElementRef, platform: Platform, ngZone: NgZone, animationMode?: string) {

src/material/button/button.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,7 @@ import {
1818
} from '@angular/core';
1919
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
2020

21-
import {
22-
MAT_ANCHOR_HOST,
23-
MAT_ANCHOR_INPUTS,
24-
MAT_BUTTON_HOST,
25-
MAT_BUTTON_INPUTS,
26-
MatAnchorBase,
27-
MatButtonBase,
28-
} from './button-base';
21+
import {MAT_ANCHOR_HOST, MAT_BUTTON_HOST, MatAnchorBase, MatButtonBase} from './button-base';
2922

3023
/**
3124
* Material Design button component. Users interact with a button to perform an action.
@@ -43,7 +36,6 @@ import {
4336
`,
4437
templateUrl: 'button.html',
4538
styleUrls: ['button.css', 'button-high-contrast.css'],
46-
inputs: MAT_BUTTON_INPUTS,
4739
host: MAT_BUTTON_HOST,
4840
exportAs: 'matButton',
4941
encapsulation: ViewEncapsulation.None,
@@ -74,7 +66,6 @@ export class MatButton extends MatButtonBase {
7466
selector: `a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button]`,
7567
exportAs: 'matButton, matAnchor',
7668
host: MAT_ANCHOR_HOST,
77-
inputs: MAT_ANCHOR_INPUTS,
7869
templateUrl: 'button.html',
7970
styleUrls: ['button.css', 'button-high-contrast.css'],
8071
encapsulation: ViewEncapsulation.None,

src/material/button/fab.ts

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,17 @@ import {
1313
ElementRef,
1414
Inject,
1515
InjectionToken,
16+
Input,
1617
NgZone,
1718
Optional,
1819
ViewEncapsulation,
20+
booleanAttribute,
1921
} from '@angular/core';
2022
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
2123

2224
import {MatAnchor} from './button';
23-
import {
24-
MAT_ANCHOR_HOST,
25-
MAT_ANCHOR_INPUTS,
26-
MAT_BUTTON_HOST,
27-
MAT_BUTTON_INPUTS,
28-
MatButtonBase,
29-
} from './button-base';
25+
import {MAT_ANCHOR_HOST, MAT_BUTTON_HOST, MatButtonBase} from './button-base';
3026
import {ThemePalette} from '@angular/material/core';
31-
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
3227

3328
/** Default FAB options that can be overridden. */
3429
export interface MatFabDefaultOptions {
@@ -55,8 +50,6 @@ export function MAT_FAB_DEFAULT_OPTIONS_FACTORY(): MatFabDefaultOptions {
5550
// Default FAB configuration.
5651
const defaults = MAT_FAB_DEFAULT_OPTIONS_FACTORY();
5752

58-
let buttonInputs = [...MAT_ANCHOR_INPUTS, 'extended'];
59-
6053
/**
6154
* Material Design floating action button (FAB) component. These buttons represent the primary
6255
* or most common action for users to interact with.
@@ -68,7 +61,6 @@ let buttonInputs = [...MAT_ANCHOR_INPUTS, 'extended'];
6861
selector: `button[mat-fab]`,
6962
templateUrl: 'button.html',
7063
styleUrls: ['fab.css'],
71-
inputs: buttonInputs,
7264
host: {
7365
...MAT_BUTTON_HOST,
7466
'[class.mdc-fab--extended]': 'extended',
@@ -81,13 +73,7 @@ let buttonInputs = [...MAT_ANCHOR_INPUTS, 'extended'];
8173
export class MatFabButton extends MatButtonBase {
8274
override _isFab = true;
8375

84-
get extended(): boolean {
85-
return this._extended;
86-
}
87-
set extended(value: BooleanInput) {
88-
this._extended = coerceBooleanProperty(value);
89-
}
90-
private _extended: boolean;
76+
@Input({transform: booleanAttribute}) extended: boolean;
9177

9278
constructor(
9379
elementRef: ElementRef,
@@ -98,7 +84,7 @@ export class MatFabButton extends MatButtonBase {
9884
) {
9985
super(elementRef, platform, ngZone, animationMode);
10086
this._options = this._options || defaults;
101-
this.color = this.defaultColor = this._options!.color || defaults.color;
87+
this.color = this._options!.color || defaults.color;
10288
}
10389
}
10490

@@ -111,7 +97,6 @@ export class MatFabButton extends MatButtonBase {
11197
selector: `button[mat-mini-fab]`,
11298
templateUrl: 'button.html',
11399
styleUrls: ['fab.css'],
114-
inputs: MAT_BUTTON_INPUTS,
115100
host: MAT_BUTTON_HOST,
116101
exportAs: 'matButton',
117102
encapsulation: ViewEncapsulation.None,
@@ -129,7 +114,7 @@ export class MatMiniFabButton extends MatButtonBase {
129114
) {
130115
super(elementRef, platform, ngZone, animationMode);
131116
this._options = this._options || defaults;
132-
this.color = this.defaultColor = this._options!.color || defaults.color;
117+
this.color = this._options!.color || defaults.color;
133118
}
134119
}
135120

@@ -144,7 +129,6 @@ export class MatMiniFabButton extends MatButtonBase {
144129
selector: `a[mat-fab]`,
145130
templateUrl: 'button.html',
146131
styleUrls: ['fab.css'],
147-
inputs: buttonInputs,
148132
host: {
149133
...MAT_ANCHOR_HOST,
150134
'[class.mdc-fab--extended]': 'extended',
@@ -157,13 +141,7 @@ export class MatMiniFabButton extends MatButtonBase {
157141
export class MatFabAnchor extends MatAnchor {
158142
override _isFab = true;
159143

160-
get extended(): boolean {
161-
return this._extended;
162-
}
163-
set extended(value: BooleanInput) {
164-
this._extended = coerceBooleanProperty(value);
165-
}
166-
private _extended: boolean;
144+
@Input({transform: booleanAttribute}) extended: boolean;
167145

168146
constructor(
169147
elementRef: ElementRef,
@@ -174,7 +152,7 @@ export class MatFabAnchor extends MatAnchor {
174152
) {
175153
super(elementRef, platform, ngZone, animationMode);
176154
this._options = this._options || defaults;
177-
this.color = this.defaultColor = this._options!.color || defaults.color;
155+
this.color = this._options!.color || defaults.color;
178156
}
179157
}
180158

@@ -187,7 +165,6 @@ export class MatFabAnchor extends MatAnchor {
187165
selector: `a[mat-mini-fab]`,
188166
templateUrl: 'button.html',
189167
styleUrls: ['fab.css'],
190-
inputs: MAT_ANCHOR_INPUTS,
191168
host: MAT_ANCHOR_HOST,
192169
exportAs: 'matButton, matAnchor',
193170
encapsulation: ViewEncapsulation.None,
@@ -205,6 +182,6 @@ export class MatMiniFabAnchor extends MatAnchor {
205182
) {
206183
super(elementRef, platform, ngZone, animationMode);
207184
this._options = this._options || defaults;
208-
this.color = this.defaultColor = this._options!.color || defaults.color;
185+
this.color = this._options!.color || defaults.color;
209186
}
210187
}

src/material/button/icon-button.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,7 @@ import {
1717
ViewEncapsulation,
1818
} from '@angular/core';
1919
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';
20-
21-
import {
22-
MAT_ANCHOR_HOST,
23-
MAT_ANCHOR_INPUTS,
24-
MAT_BUTTON_HOST,
25-
MAT_BUTTON_INPUTS,
26-
MatAnchorBase,
27-
MatButtonBase,
28-
} from './button-base';
20+
import {MAT_ANCHOR_HOST, MAT_BUTTON_HOST, MatAnchorBase, MatButtonBase} from './button-base';
2921

3022
/**
3123
* Material Design icon button component. This type of button displays a single interactive icon for
@@ -36,7 +28,6 @@ import {
3628
selector: `button[mat-icon-button]`,
3729
templateUrl: 'icon-button.html',
3830
styleUrls: ['icon-button.css', 'button-high-contrast.css'],
39-
inputs: MAT_BUTTON_INPUTS,
4031
host: MAT_BUTTON_HOST,
4132
exportAs: 'matButton',
4233
encapsulation: ViewEncapsulation.None,
@@ -64,7 +55,6 @@ export class MatIconButton extends MatButtonBase {
6455
selector: `a[mat-icon-button]`,
6556
templateUrl: 'button.html',
6657
styleUrls: ['icon-button.css', 'button-high-contrast.css'],
67-
inputs: MAT_ANCHOR_INPUTS,
6858
host: MAT_ANCHOR_HOST,
6959
exportAs: 'matButton, matAnchor',
7060
encapsulation: ViewEncapsulation.None,

src/material/button/testing/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ ts_library(
99
exclude = ["**/*.spec.ts"],
1010
),
1111
deps = [
12-
"//src/cdk/coercion",
1312
"//src/cdk/testing",
13+
"@npm//@angular/core",
1414
],
1515
)
1616

src/material/button/testing/button-harness.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {booleanAttribute} from '@angular/core';
910
import {
1011
ComponentHarnessConstructor,
1112
ContentContainerComponentHarness,
1213
HarnessPredicate,
1314
} from '@angular/cdk/testing';
14-
import {coerceBooleanProperty} from '@angular/cdk/coercion';
1515
import {ButtonHarnessFilters, ButtonVariant} from './button-harness-filters';
1616

1717
/** Harness for interacting with a MDC-based mat-button in tests. */
@@ -61,7 +61,7 @@ export class MatButtonHarness extends ContentContainerComponentHarness {
6161
/** Gets a boolean promise indicating if the button is disabled. */
6262
async isDisabled(): Promise<boolean> {
6363
const disabled = (await this.host()).getAttribute('disabled');
64-
return coerceBooleanProperty(await disabled);
64+
return booleanAttribute(await disabled);
6565
}
6666

6767
/** Gets a promise for the button's label text. */

0 commit comments

Comments
 (0)