Skip to content

perf(mdc-checkbox, mdc-radio): Use class for MDC adapter #19980

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
79 changes: 52 additions & 27 deletions src/material-experimental/mdc-checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,55 @@ const RIPPLE_ANIMATION_CONFIG: RippleAnimationConfig = {
exitDuration: numbers.FG_DEACTIVATION_MS,
};

/** @docs-private */
class CheckboxAdapter implements MDCCheckboxAdapter {
constructor(private readonly _delegate: MatCheckbox) {}

addClass(className: string) {
return this._delegate._setClass(className, true);
}

removeClass(className: string) {
return this._delegate._setClass(className, false);
}

forceLayout() {
return this._delegate._checkbox.nativeElement.offsetWidth;
}

hasNativeControl() {
return !!this._delegate._nativeCheckbox;
}

isAttachedToDOM() {
return !!this._delegate._checkbox.nativeElement.parentNode;
}

isChecked() {
return this._delegate.checked;
}

isIndeterminate() {
return this._delegate.indeterminate;
}

removeNativeControlAttr(attr: string) {
if (!this._delegate._attrBlocklist.has(attr)) {
this._delegate._nativeCheckbox.nativeElement.removeAttribute(attr);
}
}

setNativeControlAttr(attr: string, value: string) {
if (!this._delegate._attrBlocklist.has(attr)) {
this._delegate._nativeCheckbox.nativeElement.setAttribute(attr, value);
}
}

setNativeControlDisabled(disabled: boolean) {
this._delegate.disabled = disabled;
}
}

@Component({
selector: 'mat-checkbox',
templateUrl: 'checkbox.html',
Expand Down Expand Up @@ -207,31 +256,7 @@ export class MatCheckbox implements AfterViewInit, OnDestroy, ControlValueAccess
* MDC uses animation events to determine when to update `aria-checked` which is unreliable.
* Therefore we disable it and handle it ourselves.
*/
private _attrBlacklist = new Set(['aria-checked']);

/** The `MDCCheckboxAdapter` instance for this checkbox. */
private _checkboxAdapter: MDCCheckboxAdapter = {
addClass: (className) => this._setClass(className, true),
removeClass: (className) => this._setClass(className, false),
forceLayout: () => this._checkbox.nativeElement.offsetWidth,
hasNativeControl: () => !!this._nativeCheckbox,
isAttachedToDOM: () => !!this._checkbox.nativeElement.parentNode,
isChecked: () => this.checked,
isIndeterminate: () => this.indeterminate,
removeNativeControlAttr:
(attr) => {
if (!this._attrBlacklist.has(attr)) {
this._nativeCheckbox.nativeElement.removeAttribute(attr);
}
},
setNativeControlAttr:
(attr, value) => {
if (!this._attrBlacklist.has(attr)) {
this._nativeCheckbox.nativeElement.setAttribute(attr, value);
}
},
setNativeControlDisabled: (disabled) => this.disabled = disabled,
};
readonly _attrBlocklist: ReadonlySet<string> = new Set(['aria-checked']);

constructor(
private _changeDetectorRef: ChangeDetectorRef,
Expand All @@ -248,7 +273,7 @@ export class MatCheckbox implements AfterViewInit, OnDestroy, ControlValueAccess
// Note: We don't need to set up the MDCFormFieldFoundation. Its only purpose is to manage the
// ripple, which we do ourselves instead.
this.tabIndex = parseInt(tabIndex) || 0;
this._checkboxFoundation = new MDCCheckboxFoundation(this._checkboxAdapter);
this._checkboxFoundation = new MDCCheckboxFoundation(new CheckboxAdapter(this));

this._options = this._options || {};

Expand Down Expand Up @@ -375,7 +400,7 @@ export class MatCheckbox implements AfterViewInit, OnDestroy, ControlValueAccess
}

/** Sets whether the given CSS class should be applied to the native input. */
private _setClass(cssClass: string, active: boolean) {
_setClass(cssClass: string, active: boolean) {
this._classes[cssClass] = active;
this._changeDetectorRef.markForCheck();
}
Expand Down
36 changes: 22 additions & 14 deletions src/material-experimental/mdc-radio/radio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,24 @@ const RIPPLE_ANIMATION_CONFIG: RippleAnimationConfig = {
exitDuration: numbers.FG_DEACTIVATION_MS
};

/** @docs-private */
class RadioAdapter implements MDCRadioAdapter {
constructor(private readonly _delegate: MatRadioButton) {}
addClass(className: string) {
return this._delegate._setClass(className, true);
}
removeClass(className: string) {
return this._delegate._setClass(className, false);
}
setNativeControlDisabled(disabled: boolean) {
if (this._delegate.disabled !== disabled) {
this._delegate.disabled = disabled;
this._delegate._changeDetector.markForCheck();
}
}
}


/**
* A group of radio buttons. May contain one or more `<mat-radio-button>` elements.
*/
Expand Down Expand Up @@ -112,33 +130,23 @@ export class MatRadioGroup extends _MatRadioGroupBase<MatRadioButton> {
})
export class MatRadioButton extends _MatRadioButtonBase implements AfterViewInit, OnDestroy {

private _radioAdapter: MDCRadioAdapter = {
addClass: (className: string) => this._setClass(className, true),
removeClass: (className: string) => this._setClass(className, false),
setNativeControlDisabled: (disabled: boolean) => {
if (this.disabled !== disabled) {
this.disabled = disabled;
this._changeDetector.markForCheck();
}
},
};

/** Configuration for the underlying ripple. */
_rippleAnimation: RippleAnimationConfig = RIPPLE_ANIMATION_CONFIG;

_radioFoundation = new MDCRadioFoundation(this._radioAdapter);
_radioFoundation: MDCRadioFoundation;
_classes: {[key: string]: boolean} = {};

constructor(@Optional() @Inject(MAT_RADIO_GROUP) radioGroup: MatRadioGroup,
elementRef: ElementRef,
_changeDetector: ChangeDetectorRef,
readonly _changeDetector: ChangeDetectorRef,
_focusMonitor: FocusMonitor,
_radioDispatcher: UniqueSelectionDispatcher,
@Optional() @Inject(ANIMATION_MODULE_TYPE) _animationMode?: string,
@Optional() @Inject(MAT_RADIO_DEFAULT_OPTIONS)
_providerOverride?: MatRadioDefaultOptions) {
super(radioGroup, elementRef, _changeDetector, _focusMonitor,
_radioDispatcher, _animationMode, _providerOverride);
this._radioFoundation = new MDCRadioFoundation(new RadioAdapter(this));
}

ngAfterViewInit() {
Expand All @@ -151,7 +159,7 @@ export class MatRadioButton extends _MatRadioButtonBase implements AfterViewInit
this._radioFoundation.destroy();
}

private _setClass(cssClass: string, active: boolean) {
_setClass(cssClass: string, active: boolean) {
this._classes = {...this._classes, [cssClass]: active};
this._changeDetector.markForCheck();
}
Expand Down