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;