From c75280851379cbca12aef53e985dff93a947ec72 Mon Sep 17 00:00:00 2001 From: Wagner Maciel Date: Wed, 19 Jul 2023 10:46:11 -0400 Subject: [PATCH] refactor(material/button-toggle): switch to lazy ripple rendering --- src/material/button-toggle/button-toggle.html | 4 --- .../button-toggle/button-toggle.spec.ts | 10 +++++++ src/material/button-toggle/button-toggle.ts | 27 +++++++++++++++++-- src/material/core/private/ripple-loader.ts | 7 ++++- .../material/button-toggle.md | 7 ++++- tools/public_api_guard/material/core.md | 1 + 6 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/material/button-toggle/button-toggle.html b/src/material/button-toggle/button-toggle.html index 9a2113709715..297856662c20 100644 --- a/src/material/button-toggle/button-toggle.html +++ b/src/material/button-toggle/button-toggle.html @@ -14,7 +14,3 @@ - - diff --git a/src/material/button-toggle/button-toggle.spec.ts b/src/material/button-toggle/button-toggle.spec.ts index 9b38f86d4669..73167c39bc7a 100644 --- a/src/material/button-toggle/button-toggle.spec.ts +++ b/src/material/button-toggle/button-toggle.spec.ts @@ -196,11 +196,20 @@ describe('MatButtonToggle with forms', () => { expect(testComponent.modelValue).toBe('green'); })); + it('should lazily render the mat-ripple', () => { + const groupElement = groupDebugElement.nativeElement; + + expect(groupElement.querySelectorAll('.mat-ripple').length).toBe(0); + dispatchMouseEvent(innerButtons[0], 'mouseenter'); + expect(groupElement.querySelectorAll('.mat-ripple').length).toBe(1); + }); + it('should show a ripple on label click', () => { const groupElement = groupDebugElement.nativeElement; expect(groupElement.querySelectorAll('.mat-ripple-element').length).toBe(0); + dispatchMouseEvent(innerButtons[0], 'mouseenter'); dispatchMouseEvent(innerButtons[0], 'mousedown'); dispatchMouseEvent(innerButtons[0], 'mouseup'); @@ -215,6 +224,7 @@ describe('MatButtonToggle with forms', () => { expect(groupElement.querySelectorAll('.mat-ripple-element').length).toBe(0); + dispatchMouseEvent(innerButtons[0], 'mouseenter'); dispatchMouseEvent(innerButtons[0], 'mousedown'); dispatchMouseEvent(innerButtons[0], 'mouseup'); diff --git a/src/material/button-toggle/button-toggle.ts b/src/material/button-toggle/button-toggle.ts index 2bcae34354bd..6a1116c5c522 100644 --- a/src/material/button-toggle/button-toggle.ts +++ b/src/material/button-toggle/button-toggle.ts @@ -31,9 +31,11 @@ import { InjectionToken, Inject, AfterViewInit, + inject, + DoCheck, } from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; -import {CanDisableRipple, mixinDisableRipple} from '@angular/material/core'; +import {CanDisableRipple, MatRippleLoader, mixinDisableRipple} from '@angular/material/core'; /** * @deprecated No longer used. @@ -414,7 +416,7 @@ const _MatButtonToggleBase = mixinDisableRipple(class {}); }) export class MatButtonToggle extends _MatButtonToggleBase - implements OnInit, AfterViewInit, CanDisableRipple, OnDestroy + implements OnInit, AfterViewInit, CanDisableRipple, OnDestroy, DoCheck { private _checked = false; @@ -495,6 +497,12 @@ export class MatButtonToggle @Output() readonly change: EventEmitter = new EventEmitter(); + /** + * Handles the lazy creation of the MatButtonToggle ripple. + * Used to improve initial load time of large applications. + */ + _rippleLoader: MatRippleLoader = inject(MatRippleLoader); + constructor( @Optional() @Inject(MAT_BUTTON_TOGGLE_GROUP) toggleGroup: MatButtonToggleGroup, private _changeDetectorRef: ChangeDetectorRef, @@ -533,6 +541,12 @@ export class MatButtonToggle ngAfterViewInit() { this._focusMonitor.monitor(this._elementRef, true); + + this._rippleLoader?.configureRipple(this._elementRef.nativeElement, { + className: 'mat-button-toggle-ripple', + disabled: this.disableRipple || this.disabled, + trigger: this._buttonElement.nativeElement, + }); } ngOnDestroy() { @@ -547,6 +561,15 @@ export class MatButtonToggle } } + ngDoCheck(): void { + if (this._buttonElement) { + this._rippleLoader.setDisabled( + this._elementRef.nativeElement, + this.disableRipple || this.disabled, + ); + } + } + /** Focuses the button. */ focus(options?: FocusOptions): void { this._buttonElement.nativeElement.focus(options); diff --git a/src/material/core/private/ripple-loader.ts b/src/material/core/private/ripple-loader.ts index 17f03e218662..681e3c3d5755 100644 --- a/src/material/core/private/ripple-loader.ts +++ b/src/material/core/private/ripple-loader.ts @@ -76,6 +76,7 @@ export class MatRippleLoader implements OnDestroy { className?: string; centered?: boolean; disabled?: boolean; + trigger?: HTMLElement; }, ): void { // Indicates that the ripple has not yet been rendered for this component. @@ -94,6 +95,10 @@ export class MatRippleLoader implements OnDestroy { if (config.disabled) { host.setAttribute(matRippleDisabled, ''); } + + if (config.trigger) { + (host as any).matRippleTrigger = config.trigger; + } } /** Returns the ripple instance for the given host element. */ @@ -159,7 +164,7 @@ export class MatRippleLoader implements OnDestroy { this._animationMode ? this._animationMode : undefined, ); ripple._isInitialized = true; - ripple.trigger = host; + ripple.trigger = (host as any).matRippleTrigger || host; ripple.centered = host.hasAttribute(matRippleCentered); ripple.disabled = host.hasAttribute(matRippleDisabled); this.attachRipple(host, ripple); diff --git a/tools/public_api_guard/material/button-toggle.md b/tools/public_api_guard/material/button-toggle.md index 7eda92e9e11c..6b569a7e207b 100644 --- a/tools/public_api_guard/material/button-toggle.md +++ b/tools/public_api_guard/material/button-toggle.md @@ -12,12 +12,14 @@ import { CanDisableRipple } from '@angular/material/core'; import { ChangeDetectorRef } from '@angular/core'; import { _Constructor } from '@angular/material/core'; import { ControlValueAccessor } from '@angular/forms'; +import { DoCheck } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EventEmitter } from '@angular/core'; import { FocusMonitor } from '@angular/cdk/a11y'; import * as i0 from '@angular/core'; import * as i2 from '@angular/material/core'; import { InjectionToken } from '@angular/core'; +import { MatRippleLoader } from '@angular/material/core'; import { OnDestroy } from '@angular/core'; import { OnInit } from '@angular/core'; import { QueryList } from '@angular/core'; @@ -32,7 +34,7 @@ export const MAT_BUTTON_TOGGLE_GROUP: InjectionToken; export const MAT_BUTTON_TOGGLE_GROUP_VALUE_ACCESSOR: any; // @public -export class MatButtonToggle extends _MatButtonToggleBase implements OnInit, AfterViewInit, CanDisableRipple, OnDestroy { +export class MatButtonToggle extends _MatButtonToggleBase implements OnInit, AfterViewInit, CanDisableRipple, OnDestroy, DoCheck { constructor(toggleGroup: MatButtonToggleGroup, _changeDetectorRef: ChangeDetectorRef, _elementRef: ElementRef, _focusMonitor: FocusMonitor, defaultTabIndex: string, defaultOptions?: MatButtonToggleDefaultOptions); get appearance(): MatButtonToggleAppearance; set appearance(value: MatButtonToggleAppearance); @@ -54,10 +56,13 @@ export class MatButtonToggle extends _MatButtonToggleBase implements OnInit, Aft // (undocumented) ngAfterViewInit(): void; // (undocumented) + ngDoCheck(): void; + // (undocumented) ngOnDestroy(): void; // (undocumented) ngOnInit(): void; _onButtonClick(): void; + _rippleLoader: MatRippleLoader; tabIndex: number | null; value: any; // (undocumented) diff --git a/tools/public_api_guard/material/core.md b/tools/public_api_guard/material/core.md index 4844f95ca2f0..010d0648ddb6 100644 --- a/tools/public_api_guard/material/core.md +++ b/tools/public_api_guard/material/core.md @@ -404,6 +404,7 @@ export class MatRippleLoader implements OnDestroy { className?: string; centered?: boolean; disabled?: boolean; + trigger?: HTMLElement; }): void; createRipple(host: HTMLElement): MatRipple | undefined; getRipple(host: HTMLElement): MatRipple | undefined;