Skip to content

Commit 4f755cf

Browse files
devversionvivian-hu-zz
authored andcommitted
feat(ripples): support updating global ripple options at runtime (#14705)
* feat(ripples): support updating global ripple options at runtime * Allows updating any global ripple option at runtime. This makes it possible for developers to disable ripples at runtime. Closes #9729 * fixup! feat(ripples): support updating global ripple options at runtime Address feedback
1 parent f6d0ea8 commit 4f755cf

File tree

11 files changed

+183
-96
lines changed

11 files changed

+183
-96
lines changed

src/dev-app/dev-app-module.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {CommonModule} from '@angular/common';
1212
import {HttpClientModule} from '@angular/common/http';
1313
import {NgModule} from '@angular/core';
1414
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
15+
import {MAT_RIPPLE_GLOBAL_OPTIONS} from '@angular/material';
1516
import {ExampleModule} from '@angular/material-examples';
1617
import {BrowserModule} from '@angular/platform-browser';
1718
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
@@ -50,6 +51,7 @@ import {ProgressBarDemo} from './progress-bar/progress-bar-demo';
5051
import {ProgressSpinnerDemo} from './progress-spinner/progress-spinner-demo';
5152
import {RadioDemo} from './radio/radio-demo';
5253
import {RippleDemo} from './ripple/ripple-demo';
54+
import {DevAppRippleOptions} from './ripple/ripple-options';
5355
import {DEV_APP_ROUTES} from './routes';
5456
import {ScreenTypeDemo} from './screen-type/screen-type-demo';
5557
import {SelectDemo} from './select/select-demo';
@@ -140,6 +142,7 @@ import {VirtualScrollDemo} from './virtual-scroll/virtual-scroll-demo';
140142
],
141143
providers: [
142144
{provide: OverlayContainer, useClass: FullscreenOverlayContainer},
145+
{provide: MAT_RIPPLE_GLOBAL_OPTIONS, useExisting: DevAppRippleOptions},
143146
],
144147
entryComponents: [
145148
ContentElementDialog,

src/dev-app/dev-app.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ <h1>Angular Material Demos</h1>
3434
<mat-icon>fullscreen</mat-icon>
3535
</button>
3636
<button mat-button (click)="toggleTheme()">{{dark ? 'Light' : 'Dark'}} theme</button>
37+
<button mat-button (click)="rippleOptions.disabled = !rippleOptions.disabled">
38+
{{rippleOptions.disabled ? 'Enable' : 'Disable'}} ripples
39+
</button>
3740
<button mat-button (click)="root.dir = (root.dir === 'rtl' ? 'ltr' : 'rtl')"
3841
title="Toggle between RTL and LTR">
3942
{{root.dir.toUpperCase()}}

src/dev-app/dev-app.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {OverlayContainer} from '@angular/cdk/overlay';
1010
import {Component, ElementRef, ViewEncapsulation} from '@angular/core';
11+
import {DevAppRippleOptions} from './ripple/ripple-options';
1112

1213
/** Root component for the dev-app demos. */
1314
@Component({
@@ -69,7 +70,8 @@ export class DevAppComponent {
6970

7071
constructor(
7172
private _element: ElementRef<HTMLElement>,
72-
private _overlayContainer: OverlayContainer) {}
73+
private _overlayContainer: OverlayContainer,
74+
public rippleOptions: DevAppRippleOptions) {}
7375

7476
toggleFullscreen() {
7577
// Cast to `any`, because the typings don't include the browser-prefixed methods.

src/dev-app/ripple/ripple-options.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Injectable} from '@angular/core';
10+
import {RippleGlobalOptions} from '@angular/material';
11+
12+
/**
13+
* Global ripple options for the dev-app. The ripple options are used as a class
14+
* so that the global options can be changed at runtime.
15+
*/
16+
@Injectable({providedIn: 'root'})
17+
export class DevAppRippleOptions implements RippleGlobalOptions {
18+
19+
/** Whether ripples should be disabled */
20+
disabled: boolean = false;
21+
}

src/lib/chips/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ ng_test_library(
5151
"@angular//packages/platform-browser/animations",
5252
"//src/cdk/a11y",
5353
"//src/cdk/bidi",
54+
"//src/lib/core",
5455
"//src/cdk/keycodes",
5556
"//src/cdk/platform",
5657
"//src/cdk/testing",

src/lib/chips/chip.spec.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {BACKSPACE, DELETE, SPACE} from '@angular/cdk/keycodes';
33
import {createKeyboardEvent, dispatchFakeEvent} from '@angular/cdk/testing';
44
import {Component, DebugElement} from '@angular/core';
55
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
6+
import {MAT_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions} from '@angular/material/core';
67
import {By} from '@angular/platform-browser';
78
import {Subject} from 'rxjs';
89
import {MatChip, MatChipEvent, MatChipSelectionChange, MatChipsModule} from './index';
@@ -13,19 +14,22 @@ describe('Chips', () => {
1314
let chipDebugElement: DebugElement;
1415
let chipNativeElement: HTMLElement;
1516
let chipInstance: MatChip;
17+
let globalRippleOptions: RippleGlobalOptions;
1618

1719
let dir = 'ltr';
1820

1921
beforeEach(async(() => {
22+
globalRippleOptions = {};
2023
TestBed.configureTestingModule({
2124
imports: [MatChipsModule],
2225
declarations: [BasicChip, SingleChip],
23-
providers: [{
24-
provide: Directionality, useFactory: () => ({
26+
providers: [
27+
{provide: MAT_RIPPLE_GLOBAL_OPTIONS, useFactory: () => globalRippleOptions},
28+
{provide: Directionality, useFactory: () => ({
2529
value: dir,
2630
change: new Subject()
27-
})
28-
}]
31+
})},
32+
]
2933
});
3034

3135
TestBed.compileComponents();
@@ -203,6 +207,13 @@ describe('Chips', () => {
203207
subscription.unsubscribe();
204208
});
205209

210+
it('should be able to disable ripples through ripple global options at runtime', () => {
211+
expect(chipInstance.rippleDisabled).toBe(false, 'Expected chip ripples to be enabled.');
212+
213+
globalRippleOptions.disabled = true;
214+
215+
expect(chipInstance.rippleDisabled).toBe(true, 'Expected chip ripples to be disabled.');
216+
});
206217
});
207218

208219
describe('keyboard behavior', () => {

src/lib/chips/chip.ts

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -123,21 +123,20 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes
123123
/** Reference to the RippleRenderer for the chip. */
124124
private _chipRipple: RippleRenderer;
125125

126-
/** Whether the ripples are globally disabled through the RippleGlobalOptions */
127-
private _ripplesGloballyDisabled = false;
128-
129126
/**
130-
* Ripple configuration for ripples that are launched on pointer down.
127+
* Ripple configuration for ripples that are launched on pointer down. The ripple config
128+
* is set to the global ripple options since we don't have any configurable options for
129+
* the chip ripples.
131130
* @docs-private
132131
*/
133-
rippleConfig: RippleConfig = {};
132+
rippleConfig: RippleConfig & RippleGlobalOptions;
134133

135134
/**
136135
* Whether ripples are disabled on interaction
137136
* @docs-private
138137
*/
139138
get rippleDisabled(): boolean {
140-
return this.disabled || this.disableRipple || this._ripplesGloballyDisabled;
139+
return this.disabled || this.disableRipple || !!this.rippleConfig.disabled;
141140
}
142141

143142
/** Whether the chip has focus. */
@@ -225,22 +224,15 @@ export class MatChip extends _MatChipMixinBase implements FocusableOption, OnDes
225224
constructor(public _elementRef: ElementRef,
226225
private _ngZone: NgZone,
227226
platform: Platform,
228-
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions) {
227+
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS)
228+
globalRippleOptions: RippleGlobalOptions | null) {
229229
super(_elementRef);
230230

231231
this._addHostClassName();
232232

233233
this._chipRipple = new RippleRenderer(this, _ngZone, _elementRef, platform);
234234
this._chipRipple.setupTriggerEvents(_elementRef.nativeElement);
235-
236-
if (globalOptions) {
237-
// TODO(paul): Do not copy each option manually. Allow dynamic global option changes: #9729
238-
this._ripplesGloballyDisabled = !!globalOptions.disabled;
239-
this.rippleConfig = {
240-
animation: globalOptions.animation,
241-
terminateOnPointerUp: globalOptions.terminateOnPointerUp,
242-
};
243-
}
235+
this.rippleConfig = globalRippleOptions || {};
244236
}
245237

246238
_addHostClassName() {

src/lib/core/ripple/ripple.md

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ and calling its `launch` method.
2020
### Ripple trigger
2121

2222
By default ripples will fade in on interaction with the directive's host element.
23-
In some situations, developers may want to show ripples on interaction with *some other* element,
23+
In some situations, developers may want to show ripples on interaction with *some other* element,
2424
but still want to have the ripples placed in another location. This can be done by specifying
2525
the `matRippleTrigger` option that expects a reference to an `HTMLElement`.
2626

@@ -29,7 +29,7 @@ the `matRippleTrigger` option that expects a reference to an `HTMLElement`.
2929
<div matRipple [matRippleTrigger]="trigger" class="my-ripple-container">
3030
<!-- This is the ripple container, but not the trigger element for ripples. -->
3131
</div>
32-
32+
3333
<div #trigger></div>
3434
</div>
3535
```
@@ -43,14 +43,14 @@ class MyComponent {
4343

4444
/** Reference to the directive instance of the ripple. */
4545
@ViewChild(MatRipple) ripple: MatRipple;
46-
46+
4747
/** Shows a centered and persistent ripple. */
4848
launchRipple() {
4949
const rippleRef = this.ripple.launch({
5050
persistent: true,
5151
centered: true
5252
});
53-
53+
5454
// Fade out the ripple later.
5555
rippleRef.fadeOut();
5656
}
@@ -91,7 +91,7 @@ const globalRippleConfig: RippleGlobalOptions = {
9191

9292
@NgModule({
9393
providers: [
94-
{provide: MAT_RIPPLE_GLOBAL_OPTIONS, useValue: globalRippleConfig}
94+
{provide: MAT_RIPPLE_GLOBAL_OPTIONS, useValue: globalRippleConfig}
9595
]
9696
})
9797
```
@@ -100,7 +100,7 @@ All available global options can be seen in the `RippleGlobalOptions` interface.
100100

101101
### Disabling animation
102102

103-
The animation of ripples can be disabled by using the `animation` global option. If the
103+
The animation of ripples can be disabled by using the `animation` global option. If the
104104
`enterDuration` and `exitDuration` is being set to `0`, ripples will just appear without any
105105
animation.
106106

@@ -139,3 +139,43 @@ const globalRippleConfig: RippleGlobalOptions = {
139139
terminateOnPointerUp: true
140140
};
141141
```
142+
143+
### Updating global options at runtime
144+
145+
To change global ripple options at runtime, just inject the `MAT_RIPPLE_GLOBAL_OPTIONS`
146+
provider and update the desired options.
147+
148+
There are various ways of injecting the global options. In order to make it easier to
149+
inject and update options at runtime, it's recommended to create a service that implements
150+
the `RippleGlobalOptions` interface.
151+
152+
```ts
153+
@Injectable({providedIn: 'root'})
154+
export class AppGlobalRippleOptions implements RippleGlobalOptions {
155+
/** Whether ripples should be disabled globally. */
156+
disabled: boolean = false;
157+
}
158+
```
159+
160+
```ts
161+
@NgModule({
162+
providers: [
163+
{provide: MAT_RIPPLE_GLOBAL_OPTIONS, useExisting: AppGlobalRippleOptions},
164+
]
165+
})
166+
export class MyModule {...}
167+
```
168+
169+
Now that the global ripple options are set to a service we can inject, the service can be
170+
used update any global ripple option at runtime.
171+
172+
```ts
173+
@Component(...)
174+
export class MyComponent {
175+
constructor(private _appRippleOptions: AppGlobalRippleOptions) {}
176+
177+
disableRipples() {
178+
this._appRippleOptions.disabled = true;
179+
}
180+
}
181+
```

src/lib/core/ripple/ripple.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export class MatRipple implements OnInit, OnDestroy, RippleTarget {
120120
constructor(private _elementRef: ElementRef<HTMLElement>,
121121
ngZone: NgZone,
122122
platform: Platform,
123-
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions,
123+
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions?: RippleGlobalOptions,
124124
@Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode?: string) {
125125

126126
this._globalOptions = globalOptions || {};

0 commit comments

Comments
 (0)