Skip to content

Commit b088604

Browse files
authoredMar 17, 2025
refactor(material/button): simplify host bindings (#30630)
Following up on #30626 (comment), these changes move the button's shared host bindings into the base class and turn some classes that are added manually into host bindings. The previous approach was only necessary with ViewEngine, because it wasn't doing inheritance of host bindings correctly and wasn't merging classes. These are no longer issues in Ivy. Finally, these changes allow us to take advantage of an upcoming framework feature: angular/angular#60267
1 parent b4fcae4 commit b088604

File tree

5 files changed

+33
-48
lines changed

5 files changed

+33
-48
lines changed
 

‎src/material/button/button-base.ts

+18-22
Original file line numberDiff line numberDiff line change
@@ -45,34 +45,30 @@ export interface MatButtonConfig {
4545
/** Injection token that can be used to provide the default options the button component. */
4646
export const MAT_BUTTON_CONFIG = new InjectionToken<MatButtonConfig>('MAT_BUTTON_CONFIG');
4747

48-
/** Shared host configuration for all buttons */
49-
export const MAT_BUTTON_HOST = {
50-
'[attr.disabled]': '_getDisabledAttribute()',
51-
'[attr.aria-disabled]': '_getAriaDisabled()',
52-
'[class.mat-mdc-button-disabled]': 'disabled',
53-
'[class.mat-mdc-button-disabled-interactive]': 'disabledInteractive',
54-
'[class._mat-animation-noopable]': '_animationMode === "NoopAnimations"',
55-
// MDC automatically applies the primary theme color to the button, but we want to support
56-
// an unthemed version. If color is undefined, apply a CSS class that makes it easy to
57-
// select and style this "theme".
58-
'[class.mat-unthemed]': '!color',
59-
// Add a class that applies to all buttons. This makes it easier to target if somebody
60-
// wants to target all Material buttons.
61-
'[class.mat-mdc-button-base]': 'true',
62-
'[class]': 'color ? "mat-" + color : ""',
63-
'[attr.tabindex]': '_getTabIndex()',
64-
};
65-
6648
function transformTabIndex(value: unknown): number | undefined {
6749
return value == null ? undefined : numberAttribute(value);
6850
}
6951

70-
/** Base class for all buttons. */
71-
@Directive()
52+
/** Base class for all buttons. */
53+
@Directive({
54+
host: {
55+
// Add a class that applies to all buttons. This makes it easier to target if somebody
56+
// wants to target all Material buttons.
57+
'class': 'mat-mdc-button-base',
58+
'[class]': 'color ? "mat-" + color : ""',
59+
'[attr.disabled]': '_getDisabledAttribute()',
60+
'[attr.aria-disabled]': '_getAriaDisabled()',
61+
'[attr.tabindex]': '_getTabIndex()',
62+
'[class.mat-mdc-button-disabled]': 'disabled',
63+
'[class.mat-mdc-button-disabled-interactive]': 'disabledInteractive',
64+
'[class.mat-unthemed]': '!color',
65+
'[class._mat-animation-noopable]': '_animationMode === "NoopAnimations"',
66+
},
67+
})
7268
export class MatButtonBase implements AfterViewInit, OnDestroy {
7369
_elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
74-
_ngZone = inject(NgZone);
75-
_animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true});
70+
protected _ngZone = inject(NgZone);
71+
protected _animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true});
7672

7773
protected readonly _config = inject(MAT_BUTTON_CONFIG, {optional: true});
7874
private readonly _focusMonitor = inject(FocusMonitor);

‎src/material/button/button.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {ChangeDetectionStrategy, Component, Input, ViewEncapsulation} from '@angular/core';
10-
import {MAT_BUTTON_HOST, MatButtonAppearance, MatButtonBase} from './button-base';
10+
import {MatButtonAppearance, MatButtonBase} from './button-base';
1111

1212
/**
1313
* Classes that need to be set for each appearance of the button.
@@ -32,7 +32,9 @@ const APPEARANCE_CLASSES: Map<MatButtonAppearance, readonly string[]> = new Map(
3232
`,
3333
templateUrl: 'button.html',
3434
styleUrls: ['button.css', 'button-high-contrast.css'],
35-
host: MAT_BUTTON_HOST,
35+
host: {
36+
'class': 'mdc-button',
37+
},
3638
exportAs: 'matButton, matAnchor',
3739
encapsulation: ViewEncapsulation.None,
3840
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -54,11 +56,7 @@ export class MatButton extends MatButtonBase {
5456

5557
constructor() {
5658
super();
57-
const element = this._elementRef.nativeElement;
58-
const inferredAppearance = _inferAppearance(element);
59-
60-
// This class is common across all the appearances so we add it ahead of time.
61-
element.classList.add('mdc-button');
59+
const inferredAppearance = _inferAppearance(this._elementRef.nativeElement);
6260

6361
// Only set the appearance if we managed to infer it from the static attributes, rather than
6462
// doing something like `setAppearance(inferredAppearance || 'text')`, because doing so can

‎src/material/button/fab.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
inject,
1717
} from '@angular/core';
1818

19-
import {MAT_BUTTON_HOST, MatButtonBase} from './button-base';
19+
import {MatButtonBase} from './button-base';
2020
import {ThemePalette} from '../core';
2121

2222
/** Default FAB options that can be overridden. */
@@ -67,7 +67,7 @@ const defaults = MAT_FAB_DEFAULT_OPTIONS_FACTORY();
6767
templateUrl: 'button.html',
6868
styleUrl: 'fab.css',
6969
host: {
70-
...MAT_BUTTON_HOST,
70+
'class': 'mdc-fab mat-mdc-fab-base mat-mdc-fab',
7171
'[class.mdc-fab--extended]': 'extended',
7272
'[class.mat-mdc-extended-fab]': 'extended',
7373
},
@@ -86,8 +86,6 @@ export class MatFabButton extends MatButtonBase {
8686

8787
constructor() {
8888
super();
89-
const element = this._elementRef.nativeElement;
90-
element.classList.add('mdc-fab', 'mat-mdc-fab-base', 'mat-mdc-fab');
9189
this._options = this._options || defaults;
9290
this.color = this._options!.color || defaults.color;
9391
}
@@ -102,7 +100,9 @@ export class MatFabButton extends MatButtonBase {
102100
selector: `button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]`,
103101
templateUrl: 'button.html',
104102
styleUrl: 'fab.css',
105-
host: MAT_BUTTON_HOST,
103+
host: {
104+
'class': 'mdc-fab mat-mdc-fab-base mdc-fab--mini mat-mdc-mini-fab',
105+
},
106106
exportAs: 'matButton, matAnchor',
107107
encapsulation: ViewEncapsulation.None,
108108
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -116,8 +116,6 @@ export class MatMiniFabButton extends MatButtonBase {
116116

117117
constructor() {
118118
super();
119-
const element = this._elementRef.nativeElement;
120-
element.classList.add('mdc-fab', 'mat-mdc-fab-base', 'mdc-fab--mini', 'mat-mdc-mini-fab');
121119
this._options = this._options || defaults;
122120
this.color = this._options!.color || defaults.color;
123121
}

‎src/material/button/icon-button.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';
10-
import {MAT_BUTTON_HOST, MatButtonBase} from './button-base';
10+
import {MatButtonBase} from './button-base';
1111

1212
/**
1313
* Material Design icon button component. This type of button displays a single interactive icon for
@@ -18,7 +18,9 @@ import {MAT_BUTTON_HOST, MatButtonBase} from './button-base';
1818
selector: `button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]`,
1919
templateUrl: 'icon-button.html',
2020
styleUrls: ['icon-button.css', 'button-high-contrast.css'],
21-
host: MAT_BUTTON_HOST,
21+
host: {
22+
'class': 'mdc-icon-button mat-mdc-icon-button',
23+
},
2224
exportAs: 'matButton, matAnchor',
2325
encapsulation: ViewEncapsulation.None,
2426
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -28,9 +30,7 @@ export class MatIconButton extends MatButtonBase {
2830

2931
constructor() {
3032
super();
31-
const element = this._elementRef.nativeElement;
32-
element.classList.add('mdc-icon-button', 'mat-mdc-icon-button');
33-
this._rippleLoader.configureRipple(element, {centered: true});
33+
this._rippleLoader.configureRipple(this._elementRef.nativeElement, {centered: true});
3434
}
3535
}
3636

‎tslint.json

-7
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,6 @@
111111
}
112112
],
113113
"Directive": [
114-
{
115-
"argument": 0,
116-
"properties": {
117-
"!host": "\\[class\\]"
118-
},
119-
"excludeFiles": ["**/dev-app/**"]
120-
},
121114
// Enforce standalone even in the dev-app.
122115
{
123116
"argument": 0,

0 commit comments

Comments
 (0)
Failed to load comments.