Skip to content

perf(slider): convert slider adapters to class objects #19985

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 12 commits into from
188 changes: 149 additions & 39 deletions src/material-experimental/mdc-slider/slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import {
Output,
SimpleChanges,
ViewChild,
ViewEncapsulation
ViewEncapsulation,
ChangeDetectorRef
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {ThemePalette} from '@angular/material/core';
Expand Down Expand Up @@ -73,6 +74,135 @@ export const MAT_SLIDER_VALUE_ACCESSOR: any = {
multi: true
};

/** @docs-private */
class SliderAdapter implements MDCSliderAdapter {

constructor(private _delegate: MatSlider) {}

hasClass(className: string) {
return this._delegate._getElementRef().nativeElement.classList.contains(className);
}

addClass(className: string) {
return this._delegate._getElementRef().nativeElement.classList.add(className);
}

removeClass(className: string) {
this._delegate._getElementRef().nativeElement.classList.remove(className);
}

getAttribute(name: string) {
return this._delegate._getElementRef().nativeElement.getAttribute(name);
}

setAttribute(name: string, value: string) {
return this._delegate._getElementRef().nativeElement.setAttribute(name, value);
}

removeAttribute(name: string) {
this._delegate._getElementRef().nativeElement.removeAttribute(name);
}

computeBoundingRect() {
return this._delegate._getElementRef().nativeElement.getBoundingClientRect();
}

getTabIndex() {
return this._delegate._getElementRef().nativeElement.tabIndex;
}

registerInteractionHandler(evtType: any, handler: (this: HTMLElement, ev: any) => any) {
// Interaction event handlers (which handle keyboard interaction) cannot be passive
// as they will prevent the default behavior. Additionally we can't run these event
// handlers outside of the Angular zone because we rely on the events to cause the
// component tree to be re-checked.
// TODO: take in the event listener options from the adapter once MDC supports it.
return this._delegate._getElementRef().nativeElement.addEventListener(
evtType, handler, activeListenerOptions);
}

deregisterInteractionHandler(evtType: any, handler: (this: HTMLElement, ev: any) => any) {
return this._delegate._getElementRef().nativeElement.removeEventListener(evtType, handler);
}

registerThumbContainerInteractionHandler
(evtType: any, handler: (this: HTMLElement, ev: any) => any) {
// The thumb container interaction handlers are currently just used for transition
// events which don't need to run in the Angular zone.
this._delegate._ngZone.runOutsideAngular(() => {
this._delegate._thumbContainer.nativeElement
.addEventListener(evtType, handler, passiveListenerOptions);
});
}

deregisterThumbContainerInteractionHandler
(evtType: any, handler: (this: HTMLElement, ev: any) => any) {
this._delegate._thumbContainer.nativeElement
.removeEventListener(evtType, handler, passiveListenerOptions);
}

registerBodyInteractionHandler(evtType: any, handler: (this: HTMLElement, ev: any) => any) {
// Body event handlers (which handle thumb sliding) cannot be passive as they will
// prevent the default behavior. Additionally we can't run these event handlers
// outside of the Angular zone because we rely on the events to cause the component
// tree to be re-checked.
document.body.addEventListener(evtType, handler);
}

deregisterBodyInteractionHandler(evtType: any, handler: (this: HTMLElement, ev: any) => any) {
return document.body.removeEventListener(evtType, handler);
}

registerResizeHandler = (handler: (this: Window, ev: UIEvent) => any) => {
// The resize handler is currently responsible for detecting slider dimension
// changes and therefore doesn't cause a value change that needs to be propagated.
this._delegate._ngZone.runOutsideAngular(() => window.addEventListener('resize', handler));
}

deregisterResizeHandler
(handler: (this: Window, ev: UIEvent) => any) {
return window.removeEventListener('resize', handler);
}
notifyInput() {
const newValue = this._delegate._getFoundation().getValue();
// MDC currently fires the input event multiple times.
// TODO(devversion): remove this check once the input notifications are fixed.
if (newValue !== this._delegate.value) {
this._delegate.value = newValue;
this._delegate.input.emit(this._delegate._createChangeEvent(newValue));
}
}
notifyChange() {
// TODO(devversion): bug in MDC where only the "change" event is emitted if a keypress
// updated the value. Material and native range sliders also emit an input event.
// Usually we sync the "value" in the "input" event, but as a workaround we now sync
// the value in the "change" event.
this._delegate.value = this._delegate._getFoundation().getValue();
this._delegate._emitChangeEvent(this._delegate.value!);
}
setThumbContainerStyleProperty
(propertyName: string, value: string | null) {
this._delegate._thumbContainer.nativeElement.style.setProperty(propertyName, value);
}
setTrackStyleProperty
(propertyName: string, value: string | null) {
this._delegate._track.nativeElement.style.setProperty(propertyName, value);
}
setMarkerValue() {
// Mark the component for check as the thumb label needs to be re-rendered.
this._delegate._getChangeDetectorRef().markForCheck();
}
setTrackMarkers
(step: number, max: number, min: number) {
this._delegate._trackMarker.nativeElement.style.setProperty(
'background', this._delegate._getTrackMarkersBackground(min, max, step));
}

isRTL() {
return this._delegate._isRtl();
}
}

/** A simple change event emitted by the MatSlider component. */
export class MatSliderChange {
/** The MatSlider that changed. */
Expand Down Expand Up @@ -223,41 +353,8 @@ export class MatSlider implements AfterViewInit, OnChanges, OnDestroy, ControlVa
}
private _disabled = false;

/** Adapter for the MDC slider foundation. */
private _sliderAdapter: MDCSliderAdapter = {
hasClass: (_className: string) => false,
addClass: (_className: string) => {},
removeClass: (_className: string) => {},
getAttribute: (_attribute: string) => null,
addThumbClass: (_className: string, _thumb: Thumb) => {},
removeThumbClass: (_className: string, _thumb: Thumb) => {},
getThumbAttribute: (_attribute: string, _thumb: Thumb) => null,
setThumbAttribute: (_attribute: string, _value: string, _thumb: Thumb) => {},
getThumbKnobWidth: (_thumb: Thumb) => 0,
isThumbFocused: (_thumb: Thumb) => false,
focusThumb: (_thumb: Thumb) => {},
getThumbBoundingClientRect: (_thumb: Thumb) => null!,
getBoundingClientRect: () => null!,
isRTL: () => false,
setThumbStyleProperty: (_propertyName: string, _value: string, _thumb: Thumb) => {},
setTrackActiveStyleProperty: (_propertyName: string, _value: string) => {},
setValueIndicatorText: (_value: number, _thumb: Thumb) => {},
updateTickMarks: () => {},
setPointerCapture: (_pointerId: number) => {},
emitChangeEvent: (_value: number, _thumb: Thumb) => {},
emitInputEvent: (_value: number, _thumb: Thumb) => {},
registerEventHandler: () => {},
deregisterEventHandler: () => {},
registerThumbEventHandler: () => {},
deregisterThumbEventHandler: () => {},
registerBodyEventHandler: () => {},
deregisterBodyEventHandler: () => {},
registerWindowEventHandler: () => {},
deregisterWindowEventHandler: () => {},
};

/** Instance of the MDC slider foundation for this slider. */
private _foundation = new MDCSliderFoundation(this._sliderAdapter);
readonly _foundation = new MDCSliderFoundation(new SliderAdapter(this));

/** Whether the MDC foundation has been initialized. */
private _isInitialized = false;
Expand All @@ -278,7 +375,8 @@ export class MatSlider implements AfterViewInit, OnChanges, OnDestroy, ControlVa

constructor(
private _elementRef: ElementRef<HTMLElement>,
private _ngZone: NgZone,
private _changeDetectorRef: ChangeDetectorRef,
readonly _ngZone: NgZone,
private _platform: Platform,
@Optional() private _dir: Directionality,
@Attribute('tabindex') tabIndex: string,
Expand Down Expand Up @@ -361,6 +459,18 @@ export class MatSlider implements AfterViewInit, OnChanges, OnDestroy, ControlVa
}
}

_getChangeDetectorRef() {
return this._changeDetectorRef;
}

_getElementRef() {
return this._elementRef;
}

_getFoundation() {
return this._foundation;
}

/** Focuses the slider. */
focus(options?: FocusOptions) {
this._elementRef.nativeElement.focus(options);
Expand All @@ -380,7 +490,7 @@ export class MatSlider implements AfterViewInit, OnChanges, OnDestroy, ControlVa
}

/** Creates a slider change object from the specified value. */
private _createChangeEvent(newValue: number): MatSliderChange {
_createChangeEvent(newValue: number): MatSliderChange {
const event = new MatSliderChange();
event.source = this;
event.value = newValue;
Expand All @@ -390,7 +500,7 @@ export class MatSlider implements AfterViewInit, OnChanges, OnDestroy, ControlVa
// TODO: disabled until we implement the new MDC slider.
/** Emits a change event and notifies the control value accessor. */
// tslint:disable-next-line:no-unused-variable
private _emitChangeEvent(newValue: number) {
_emitChangeEvent(newValue: number) {
this._controlValueAccessorChangeFn(newValue);
this.valueChange.emit(newValue);
this.change.emit(this._createChangeEvent(newValue));
Expand All @@ -399,7 +509,7 @@ export class MatSlider implements AfterViewInit, OnChanges, OnDestroy, ControlVa
// TODO: disabled until we implement the new MDC slider.
/** Computes the CSS background value for the track markers (aka ticks). */
// tslint:disable-next-line:no-unused-variable
private _getTrackMarkersBackground(min: number, max: number, step: number) {
_getTrackMarkersBackground(min: number, max: number, step: number) {
if (!this.tickInterval) {
return '';
}
Expand Down